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/Cell.py ADDED
@@ -0,0 +1,1780 @@
1
+ import topologicpy
2
+ import topologic
3
+ from topologicpy.Wire import Wire
4
+ from topologicpy.Topology import Topology
5
+ import math
6
+
7
+ class Cell(Topology):
8
+ @staticmethod
9
+ def Area(cell: topologic.Cell, mantissa: int = 4) -> float:
10
+ """
11
+ Returns the surface area of the input cell.
12
+
13
+ Parameters
14
+ ----------
15
+ cell : topologic.Cell
16
+ The cell.
17
+ mantissa : int , optional
18
+ The desired length of the mantissa. The default is 4.
19
+
20
+ Returns
21
+ -------
22
+ float
23
+ The surface area of the input cell.
24
+
25
+ """
26
+ from topologicpy.Face import Face
27
+
28
+ faces = []
29
+ _ = cell.Faces(None, faces)
30
+ area = 0.0
31
+ for aFace in faces:
32
+ area = area + Face.Area(aFace)
33
+ return round(area, mantissa)
34
+
35
+ @staticmethod
36
+ def Box(origin: topologic.Vertex = None, width: float = 1, length: float = 1, height: float = 1, uSides: int = 1, vSides:int = 1, wSides:int = 1, direction: list = [0,0,1], placement: str ="center") -> topologic.Cell:
37
+ """
38
+ Creates a box.
39
+
40
+ Parameters
41
+ ----------
42
+ origin : topologic.Vertex , optional
43
+ The origin location of the box. The default is None which results in the box being placed at (0,0,0).
44
+ width : float , optional
45
+ The width of the box. The default is 1.
46
+ length : float , optional
47
+ The length of the box. The default is 1.
48
+ height : float , optional
49
+ The height of the box.
50
+ uSides : int , optional
51
+ The number of sides along the width. The default is 1.
52
+ vSides : int , optional
53
+ The number of sides along the length. The default is 1.
54
+ wSides : int , optional
55
+ The number of sides along the height. The default is 1.
56
+ direction : list , optional
57
+ The vector representing the up direction of the box. The default is [0,0,1].
58
+ placement : str , optional
59
+ The description of the placement of the origin of the box. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
60
+
61
+ Returns
62
+ -------
63
+ topologic.Cell
64
+ The created box.
65
+
66
+ """
67
+ return Cell.Prism(origin=origin, width=width, length=length, height=height, uSides=uSides, vSides=vSides, wSides=wSides, direction=direction, placement=placement)
68
+
69
+ @staticmethod
70
+ def ByFaces(faces: list, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
71
+ """
72
+ Creates a cell from the input list of faces.
73
+
74
+ Parameters
75
+ ----------
76
+ faces : list
77
+ The input list of faces.
78
+ planarize : bool, optional
79
+ If set to True, the input faces are planarized before building the cell. Otherwise, they are not. The default is False.
80
+ tolerance : float , optional
81
+ The desired tolerance. The default is 0.0001.
82
+
83
+ Returns
84
+ -------
85
+ topologic.Cell
86
+ The created cell.
87
+
88
+ """
89
+ from topologicpy.Wire import Wire
90
+ from topologicpy.Face import Face
91
+ from topologicpy.Topology import Topology
92
+ if not isinstance(faces, list):
93
+ return None
94
+ faceList = [x for x in faces if isinstance(x, topologic.Face)]
95
+ if len(faceList) < 1:
96
+ return None
97
+ planarizedList = []
98
+ enlargedList = []
99
+ if planarize:
100
+ planarizedList = [Face.Planarize(f) for f in faceList]
101
+ enlargedList = [Face.ByOffset(f, offset=-tolerance*10) for f in planarizedList]
102
+ cell = topologic.Cell.ByFaces(enlargedList, tolerance)
103
+ faces = Topology.SubTopologies(cell, subTopologyType="face")
104
+ finalFaces = []
105
+ for f in faces:
106
+ centroid = Topology.Centroid(f)
107
+ n = Face.Normal(f)
108
+ v = Topology.Translate(centroid, n[0]*0.01,n[1]*0.01,n[2]*0.01)
109
+ if not Cell.IsInside(cell, v):
110
+ finalFaces.append(f)
111
+ finalFinalFaces = []
112
+ for f in finalFaces:
113
+ vertices = Face.Vertices(f)
114
+ w = Wire.Cycles(Face.ExternalBoundary(f), maxVertices=len(vertices))[0]
115
+ finalFinalFaces.append(Face.ByWire(w))
116
+ return topologic.Cell.ByFaces(finalFinalFaces, tolerance)
117
+ else:
118
+ return topologic.Cell.ByFaces(faces, tolerance)
119
+
120
+ @staticmethod
121
+ def ByShell(shell: topologic.Shell, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
122
+ """
123
+ Creates a cell from the input shell.
124
+
125
+ Parameters
126
+ ----------
127
+ shell : topologic.Shell
128
+ The input shell. The shell must be closed for this method to succeed.
129
+ planarize : bool, optional
130
+ If set to True, the input faces of the input shell are planarized before building the cell. Otherwise, they are not. The default is False.
131
+ tolerance : float , optional
132
+ The desired tolerance. The default is 0.0001.
133
+
134
+ Returns
135
+ -------
136
+ topologic.Cell
137
+ The created cell.
138
+
139
+ """
140
+ from topologicpy.Topology import Topology
141
+ if not isinstance(shell, topologic.Shell):
142
+ return None
143
+ faces = Topology.SubTopologies(shell, subTopologyType="face")
144
+ return Cell.ByFaces(faces, planarize=planarize, tolerance=tolerance)
145
+
146
+ @staticmethod
147
+ def ByThickenedFace(face: topologic.Face, thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
148
+ planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
149
+ """
150
+ Creates a cell by thickening the input face.
151
+
152
+ Parameters
153
+ ----------
154
+ face : topologic.Face
155
+ The input face to be thickened.
156
+ thickness : float , optional
157
+ The desired thickness. The default is 1.0.
158
+ bothSides : bool
159
+ If True, the cell will be lofted to each side of the face. Otherwise, it will be lofted in the direction of the normal to the input face. The default is True.
160
+ reverse : bool
161
+ If True, the cell will be lofted in the opposite direction of the normal to the face. The default is False.
162
+ planarize : bool, optional
163
+ If set to True, the input faces of the input shell are planarized before building the cell. Otherwise, they are not. The default is False.
164
+ tolerance : float , optional
165
+ The desired tolerance. The default is 0.0001.
166
+
167
+ Returns
168
+ -------
169
+ topologic.Cell
170
+ The created cell.
171
+
172
+ """
173
+ from topologicpy.Edge import Edge
174
+ from topologicpy.Face import Face
175
+ from topologicpy.Cluster import Cluster
176
+ from topologicpy.Topology import Topology
177
+
178
+ if not isinstance(face, topologic.Face):
179
+ return None
180
+ if reverse == True and bothSides == False:
181
+ thickness = -thickness
182
+ faceNormal = Face.Normal(face)
183
+ if bothSides:
184
+ bottomFace = Topology.Translate(face, -faceNormal[0]*0.5*thickness, -faceNormal[1]*0.5*thickness, -faceNormal[2]*0.5*thickness)
185
+ topFace = Topology.Translate(face, faceNormal[0]*0.5*thickness, faceNormal[1]*0.5*thickness, faceNormal[2]*0.5*thickness)
186
+ else:
187
+ bottomFace = face
188
+ topFace = Topology.Translate(face, faceNormal[0]*thickness, faceNormal[1]*thickness, faceNormal[2]*thickness)
189
+
190
+ cellFaces = [bottomFace, topFace]
191
+ bottomEdges = []
192
+ _ = bottomFace.Edges(None, bottomEdges)
193
+ for bottomEdge in bottomEdges:
194
+ topEdge = Topology.Translate(bottomEdge, faceNormal[0]*thickness, faceNormal[1]*thickness, faceNormal[2]*thickness)
195
+ sideEdge1 = Edge.ByVertices([bottomEdge.StartVertex(), topEdge.StartVertex()])
196
+ sideEdge2 = Edge.ByVertices([bottomEdge.EndVertex(), topEdge.EndVertex()])
197
+ cellWire = Cluster.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]))
198
+ cellFaces.append(Face.ByWire(cellWire))
199
+ return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
200
+
201
+ @staticmethod
202
+ def ByThickenedShell(shell: topologic.Shell, direction: list = [0,0,1], thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
203
+ planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
204
+ """
205
+ Creates a cell by thickening the input shell. The shell must be open.
206
+
207
+ Parameters
208
+ ----------
209
+ shell : topologic.Shell
210
+ The input shell to be thickened.
211
+ thickness : float , optional
212
+ The desired thickness. The default is 1.0.
213
+ bothSides : bool
214
+ If True, the cell will be lofted to each side of the shell. Otherwise, it will be lofted along the input direction. The default is True.
215
+ reverse : bool
216
+ If True, the cell will be lofted along the opposite of the input direction. The default is False.
217
+ planarize : bool, optional
218
+ If set to True, the input faces of the input shell are planarized before building the cell. Otherwise, they are not. The default is False.
219
+ tolerance : float , optional
220
+ The desired tolerance. The default is 0.0001.
221
+
222
+ Returns
223
+ -------
224
+ topologic.Cell
225
+ The created cell.
226
+
227
+ """
228
+ from topologicpy.Edge import Edge
229
+ from topologicpy.Wire import Wire
230
+ from topologicpy.Face import Face
231
+ from topologicpy.Shell import Shell
232
+ from topologicpy.Cluster import Cluster
233
+ from topologicpy.Topology import Topology
234
+ if not isinstance(shell, topologic.Shell):
235
+ return None
236
+ if reverse == True and bothSides == False:
237
+ thickness = -thickness
238
+ if bothSides:
239
+ bottomShell = Topology.Translate(shell, -direction[0]*0.5*thickness, -direction[1]*0.5*thickness, -direction[2]*0.5*thickness)
240
+ topShell = Topology.Translate(shell, direction[0]*0.5*thickness, direction[1]*0.5*thickness, direction[2]*0.5*thickness)
241
+ else:
242
+ bottomShell = shell
243
+ topShell = Topology.Translate(shell, direction[0]*thickness, direction[1]*thickness, direction[2]*thickness)
244
+ cellFaces = Shell.Faces(bottomShell) + Shell.Faces(topShell)
245
+ bottomWire = Shell.ExternalBoundary(bottomShell)
246
+ bottomEdges = Wire.Edges(bottomWire)
247
+ for bottomEdge in bottomEdges:
248
+ topEdge = Topology.Translate(bottomEdge, direction[0]*thickness, direction[1]*thickness, direction[2]*thickness)
249
+ sideEdge1 = Edge.ByVertices([Edge.StartVertex(bottomEdge), Edge.StartVertex(topEdge)])
250
+ sideEdge2 = Edge.ByVertices([Edge.EndVertex(bottomEdge), Edge.EndVertex(topEdge)])
251
+ cellWire = Cluster.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]))
252
+ cellFace = Face.ByWire(cellWire)
253
+ cellFaces.append(cellFace)
254
+ return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
255
+
256
+ @staticmethod
257
+ def ByWires(wires: list, close: bool = False, triangulate: bool = True, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
258
+ """
259
+ Creates a cell by lofting through the input list of wires.
260
+
261
+ Parameters
262
+ ----------
263
+ wires : topologic.Wire
264
+ The input list of wires.
265
+ close : bool , optional
266
+ If set to True, the last wire in the list of input wires will be connected to the first wire in the list of input wires. The default is False.
267
+ triangulate : bool , optional
268
+ If set to True, the faces will be triangulated. The default is True.
269
+ tolerance : float , optional
270
+ The desired tolerance. The default is 0.0001.
271
+
272
+ Raises
273
+ ------
274
+ Exception
275
+ Raises an exception if the two wires in the list do not have the same number of edges.
276
+
277
+ Returns
278
+ -------
279
+ topologic.Cell
280
+ The created cell.
281
+
282
+ """
283
+
284
+ def cleanup(f):
285
+ flatFace = Face.Flatten(f)
286
+ world_origin = Vertex.ByCoordinates(0,0,0)
287
+ # Retrieve the needed transformations
288
+ dictionary = Topology.Dictionary(flatFace)
289
+ xTran = Dictionary.ValueAtKey(dictionary,"xTran")
290
+ yTran = Dictionary.ValueAtKey(dictionary,"yTran")
291
+ zTran = Dictionary.ValueAtKey(dictionary,"zTran")
292
+ phi = Dictionary.ValueAtKey(dictionary,"phi")
293
+ theta = Dictionary.ValueAtKey(dictionary,"theta")
294
+
295
+ f = Topology.Rotate(f, origin=world_origin, x=0, y=1, z=0, degree=theta)
296
+ f = Topology.Rotate(f, origin=world_origin, x=0, y=0, z=1, degree=phi)
297
+ f = Topology.Translate(f, xTran, yTran, zTran)
298
+ return f
299
+
300
+ from topologicpy.Vertex import Vertex
301
+ from topologicpy.Edge import Edge
302
+ from topologicpy.Wire import Wire
303
+ from topologicpy.Face import Face
304
+ from topologicpy.Shell import Shell
305
+ from topologicpy.Cluster import Cluster
306
+ from topologicpy.Topology import Topology
307
+ from topologicpy.Dictionary import Dictionary
308
+
309
+ faces = [Face.ByWire(wires[0]), Face.ByWire(wires[-1])]
310
+ if close == True:
311
+ faces.append(Face.ByWire(wires[0]))
312
+ if triangulate == True:
313
+ triangles = []
314
+ for face in faces:
315
+ if len(Topology.Vertices(face)) > 3:
316
+ triangles += Face.Triangulate(face)
317
+ else:
318
+ triangles += [face]
319
+ faces = triangles
320
+ for i in range(len(wires)-1):
321
+ wire1 = wires[i]
322
+ wire2 = wires[i+1]
323
+ w1_edges = []
324
+ _ = wire1.Edges(None, w1_edges)
325
+ w2_edges = []
326
+ _ = wire2.Edges(None, w2_edges)
327
+ if len(w1_edges) != len(w2_edges):
328
+ return None
329
+ if triangulate == True:
330
+ for j in range (len(w1_edges)):
331
+ e1 = w1_edges[j]
332
+ e2 = w2_edges[j]
333
+ e3 = None
334
+ e4 = None
335
+ try:
336
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
337
+ except:
338
+ try:
339
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
340
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e4])))
341
+ except:
342
+ pass
343
+ try:
344
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
345
+ except:
346
+ try:
347
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
348
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e3])))
349
+ except:
350
+ pass
351
+ if e3 and e4:
352
+ e5 = Edge.ByVertices([e1.StartVertex(), e2.EndVertex()])
353
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e5, e4])))
354
+ faces.append(Face.ByWire(Wire.ByEdges([e2, e5, e3])))
355
+ else:
356
+ for j in range (len(w1_edges)):
357
+ e1 = w1_edges[j]
358
+ e2 = w2_edges[j]
359
+ e3 = None
360
+ e4 = None
361
+ try:
362
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
363
+ except:
364
+ try:
365
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
366
+ except:
367
+ pass
368
+ try:
369
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
370
+ except:
371
+ try:
372
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
373
+ except:
374
+ pass
375
+ if e3 and e4:
376
+ try:
377
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2, e3])))
378
+ except:
379
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2, e4])))
380
+ elif e3:
381
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2])))
382
+ elif e4:
383
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2])))
384
+ #for f in faces:
385
+ #cleanup(f)
386
+ cell = Cell.ByFaces(faces, planarize=planarize, tolerance=tolerance)
387
+ if not cell:
388
+ cell = Shell.ByFaces(faces)
389
+ if cell:
390
+ geom = Topology.Geometry(cell)
391
+ cell = Topology.ByGeometry(geom['vertices'], geom['edges'], geom['faces'])
392
+ elif not isinstance(cell, topologic.Cell):
393
+ cell = Shell.ByFaces(faces)
394
+ if not cell:
395
+ cell = Cluster.ByTopologies(faces)
396
+ return cell
397
+
398
+ @staticmethod
399
+ def ByWiresCluster(cluster: topologic.Cluster, close: bool = False, triangulate: bool = True, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
400
+ """
401
+ Creates a cell by lofting through the input cluster of wires.
402
+
403
+ Parameters
404
+ ----------
405
+ cluster : topologic.Cluster
406
+ The input Cluster of wires.
407
+ close : bool , optional
408
+ If set to True, the last wire in the cluster of input wires will be connected to the first wire in the cluster of input wires. The default is False.
409
+ triangulate : bool , optional
410
+ If set to True, the faces will be triangulated. The default is True.
411
+ tolerance : float , optional
412
+ The desired tolerance. The default is 0.0001.
413
+
414
+ Raises
415
+ ------
416
+ Exception
417
+ Raises an exception if the two wires in the list do not have the same number of edges.
418
+
419
+ Returns
420
+ -------
421
+ topologic.Cell
422
+ The created cell.
423
+
424
+ """
425
+ if not isinstance(cluster, topologic.Cluster):
426
+ return None
427
+ wires = []
428
+ _ = cluster.Wires(None, wires)
429
+ return Cell.ByWires(wires, close=close, triangulate=triangulate, planarize=planarize, tolerance=tolerance)
430
+
431
+ @staticmethod
432
+ def Compactness(cell: topologic.Cell, mantissa: int = 4) -> float:
433
+ """
434
+ Returns the compactness measure of the input cell. This is also known as 'sphericity' (https://en.wikipedia.org/wiki/Sphericity).
435
+
436
+ Parameters
437
+ ----------
438
+ cell : topologic.Cell
439
+ The input cell.
440
+ mantissa : int , optional
441
+ The desired length of the mantissa. The default is 4.
442
+
443
+ Raises
444
+ ------
445
+ Exception
446
+ Raises an exception if the resulting surface area is negative. This can occur if the cell is degenerate or has flipped face normals.
447
+
448
+ Returns
449
+ -------
450
+ float
451
+ The compactness of the input cell.
452
+
453
+ """
454
+ faces = []
455
+ _ = cell.Faces(None, faces)
456
+ area = 0.0
457
+ for aFace in faces:
458
+ area = area + abs(topologic.FaceUtility.Area(aFace))
459
+ volume = abs(topologic.CellUtility.Volume(cell))
460
+ compactness = 0
461
+ #From https://en.wikipedia.org/wiki/Sphericity
462
+ if area > 0:
463
+ compactness = (((math.pi)**(1/3))*((6*volume)**(2/3)))/area
464
+ else:
465
+ raise Exception("Error: Cell.Compactness: Cell surface area is not positive")
466
+ return round(compactness, mantissa)
467
+
468
+ @staticmethod
469
+ def Cone(origin: topologic.Vertex = None, baseRadius: float = 0.5, topRadius: float = 0, height: float = 1, uSides: int = 16, vSides: int = 1, direction: list = [0,0,1],
470
+ dirZ: float = 1, placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
471
+ """
472
+ Creates a cone.
473
+
474
+ Parameters
475
+ ----------
476
+ origin : topologic.Vertex , optional
477
+ The location of the origin of the cone. The default is None which results in the cone being placed at (0,0,0).
478
+ baseRadius : float , optional
479
+ The radius of the base circle of the cone. The default is 0.5.
480
+ topRadius : float , optional
481
+ The radius of the top circle of the cone. The default is 0.
482
+ height : float , optional
483
+ The height of the cone. The default is 1.
484
+ sides : int , optional
485
+ The number of sides of the cone. The default is 16.
486
+ direction : list , optional
487
+ The vector representing the up direction of the cone. The default is [0,0,1].
488
+ placement : str , optional
489
+ The description of the placement of the origin of the cone. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
490
+ tolerance : float , optional
491
+ The desired tolerance. The default is 0.0001.
492
+
493
+ Returns
494
+ -------
495
+ topologic.Cell
496
+ The created cone.
497
+
498
+ """
499
+ from topologicpy.Vertex import Vertex
500
+ from topologicpy.Wire import Wire
501
+ from topologicpy.Face import Face
502
+ from topologicpy.Shell import Shell
503
+ from topologicpy.Cluster import Cluster
504
+ from topologicpy.Topology import Topology
505
+ def createCone(baseWire, topWire, baseVertex, topVertex, tolerance):
506
+ if baseWire == None and topWire == None:
507
+ raise Exception("Cell.Cone - Error: Both radii of the cone cannot be zero at the same time")
508
+ elif baseWire == None:
509
+ apex = baseVertex
510
+ wire = topWire
511
+ elif topWire == None:
512
+ apex = topVertex
513
+ wire = baseWire
514
+ else:
515
+ return topologic.CellUtility.ByLoft([baseWire, topWire])
516
+ vertices = []
517
+ _ = wire.Vertices(None,vertices)
518
+ faces = [Face.ByWire(wire)]
519
+ for i in range(0, len(vertices)-1):
520
+ w = Wire.ByVertices([apex, vertices[i], vertices[i+1]])
521
+ f = Face.ByWire(w)
522
+ faces.append(f)
523
+ w = Wire.ByVertices([apex, vertices[-1], vertices[0]])
524
+ f = Face.ByWire(w)
525
+ faces.append(f)
526
+ return Cell.ByFaces(faces, tolerance=tolerance)
527
+ if not origin:
528
+ origin = Vertex.ByCoordinates(0,0,0)
529
+ if not isinstance(origin, topologic.Vertex):
530
+ return None
531
+ xOffset = 0
532
+ yOffset = 0
533
+ zOffset = 0
534
+ if placement.lower() == "center":
535
+ xOffset = 0
536
+ yOffset = 0
537
+ zOffset = -height*0.5
538
+ elif placement.lower() == "lowerleft":
539
+ xOffset = max(baseRadius, topRadius)
540
+ yOffset = max(baseRadius, topRadius)
541
+ zOffset = 0
542
+
543
+ baseZ = origin.Z() + zOffset
544
+ topZ = origin.Z() + zOffset + height
545
+ baseV = []
546
+ topV = []
547
+ for i in range(uSides):
548
+ angle = math.radians(360/uSides)*i
549
+ if baseRadius > 0:
550
+ baseX = math.sin(angle)*baseRadius + origin.X() + xOffset
551
+ baseY = math.cos(angle)*baseRadius + origin.Y() + yOffset
552
+ baseZ = origin.Z() + zOffset
553
+ baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ))
554
+ if topRadius > 0:
555
+ topX = math.sin(angle)*topRadius + origin.X() + xOffset
556
+ topY = math.cos(angle)*topRadius + origin.Y() + yOffset
557
+ topV.append(Vertex.ByCoordinates(topX,topY,topZ))
558
+ if baseRadius > 0:
559
+ baseWire = Wire.ByVertices(baseV)
560
+ else:
561
+ baseWire = None
562
+ if topRadius > 0:
563
+ topWire = Wire.ByVertices(topV)
564
+ else:
565
+ topWire = None
566
+ baseVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset)
567
+ topVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset+height)
568
+ cone = createCone(baseWire, topWire, baseVertex, topVertex, tolerance)
569
+ if cone == None:
570
+ return None
571
+
572
+ if vSides > 1:
573
+ cutting_planes = []
574
+ baseX = origin.X() + xOffset
575
+ baseY = origin.Y() + yOffset
576
+ size = max(baseRadius, topRadius)*3
577
+ for i in range(1, vSides):
578
+ baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i
579
+ tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ)
580
+ cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size)))
581
+ cutting_planes_cluster = Cluster.ByTopologies(cutting_planes)
582
+ shell = Cell.Shells(cone)[0]
583
+ shell = shell.Slice(cutting_planes_cluster)
584
+ cone = Cell.ByShell(shell)
585
+ x1 = origin.X()
586
+ y1 = origin.Y()
587
+ z1 = origin.Z()
588
+ x2 = origin.X() + direction[0]
589
+ y2 = origin.Y() + direction[1]
590
+ z2 = origin.Z() + direction[2]
591
+ dx = x2 - x1
592
+ dy = y2 - y1
593
+ dz = z2 - z1
594
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
595
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
596
+ if dist < 0.0001:
597
+ theta = 0
598
+ else:
599
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
600
+ cone = Topology.Rotate(cone, origin, 0, 1, 0, theta)
601
+ cone = Topology.Rotate(cone, origin, 0, 0, 1, phi)
602
+ return cone
603
+
604
+ @staticmethod
605
+ def ContainmentStatus(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> int:
606
+ """
607
+ Returns the containment status of the input vertex in relationship to the input cell
608
+
609
+ Parameters
610
+ ----------
611
+ cell : topologic.Cell
612
+ The input cell.
613
+ vertex : topologic.Vertex
614
+ The input vertex.
615
+ tolerance : float , optional
616
+ The desired tolerance. The default is 0.0001.
617
+
618
+ Returns
619
+ -------
620
+ int
621
+ Returns 0 if the vertex is inside, 1 if it is on the boundary of, and 2 if it is outside the input cell.
622
+
623
+ """
624
+ if not cell:
625
+ return None
626
+ if not isinstance(cell, topologic.Cell):
627
+ return None
628
+ try:
629
+ status = topologic.CellUtility.Contains(cell, vertex, tolerance)
630
+ if status == 0:
631
+ return 0
632
+ elif status == 1:
633
+ return 1
634
+ else:
635
+ return 2
636
+ except:
637
+ return None
638
+
639
+ @staticmethod
640
+ def Cylinder(origin: topologic.Vertex = None, radius: float = 0.5, height: float = 1, uSides: int = 16, vSides:int = 1, direction: list = [0,0,1],
641
+ placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
642
+ """
643
+ Creates a cylinder.
644
+
645
+ Parameters
646
+ ----------
647
+ origin : topologic.Vertex , optional
648
+ The location of the origin of the cylinder. The default is None which results in the cylinder being placed at (0,0,0).
649
+ radius : float , optional
650
+ The radius of the cylinder. The default is 0.5.
651
+ height : float , optional
652
+ The height of the cylinder. The default is 1.
653
+ uSides : int , optional
654
+ The number of circle segments of the cylinder. The default is 16.
655
+ vSides : int , optional
656
+ The number of vertical segments of the cylinder. The default is 1.
657
+ direction : list , optional
658
+ The vector representing the up direction of the cylinder. The default is [0,0,1].
659
+ placement : str , optional
660
+ The description of the placement of the origin of the cylinder. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "bottom".
661
+ tolerance : float , optional
662
+ The desired tolerance. The default is 0.0001.
663
+
664
+ Returns
665
+ -------
666
+ topologic.Cell
667
+ The created cell.
668
+
669
+ """
670
+ from topologicpy.Vertex import Vertex
671
+ from topologicpy.Face import Face
672
+ from topologicpy.CellComplex import CellComplex
673
+ from topologicpy.Cluster import Cluster
674
+ from topologicpy.Topology import Topology
675
+ if not origin:
676
+ origin = Vertex.ByCoordinates(0,0,0)
677
+ if not isinstance(origin, topologic.Vertex):
678
+ return None
679
+ xOffset = 0
680
+ yOffset = 0
681
+ zOffset = 0
682
+ if placement.lower() == "center":
683
+ zOffset = -height*0.5
684
+ elif placement.lower() == "lowerleft":
685
+ xOffset = radius
686
+ yOffset = radius
687
+ circle_origin = Vertex.ByCoordinates(origin.X() + xOffset, origin.Y() + yOffset, origin.Z() + zOffset)
688
+
689
+ baseWire = Wire.Circle(origin=circle_origin, radius=radius, sides=uSides, fromAngle=0, toAngle=360, close=True, direction=[0,0,1], placement="center", tolerance=tolerance)
690
+ baseFace = Face.ByWire(baseWire)
691
+ cylinder = Cell.ByThickenedFace(face=baseFace, thickness=height, bothSides=False, reverse=False,
692
+ tolerance=tolerance)
693
+ if vSides > 1:
694
+ cutting_planes = []
695
+ baseX = origin.X() + xOffset
696
+ baseY = origin.Y() + yOffset
697
+ size = radius*3
698
+ for i in range(1, vSides):
699
+ baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i
700
+ tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ)
701
+ cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size)))
702
+ cutting_planes_cluster = Cluster.ByTopologies(cutting_planes)
703
+ cylinder = CellComplex.ExternalBoundary(cylinder.Slice(cutting_planes_cluster))
704
+
705
+ x1 = origin.X()
706
+ y1 = origin.Y()
707
+ z1 = origin.Z()
708
+ x2 = origin.X() + direction[0]
709
+ y2 = origin.Y() + direction[1]
710
+ z2 = origin.Z() + direction[2]
711
+ dx = x2 - x1
712
+ dy = y2 - y1
713
+ dz = z2 - z1
714
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
715
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
716
+ if dist < 0.0001:
717
+ theta = 0
718
+ else:
719
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
720
+ cylinder = Topology.Rotate(cylinder, origin, 0, 1, 0, theta)
721
+ cylinder = Topology.Rotate(cylinder, origin, 0, 0, 1, phi)
722
+ return cylinder
723
+
724
+ @staticmethod
725
+ def Decompose(cell: topologic.Cell, tiltAngle: float = 10, tolerance: float = 0.0001) -> dict:
726
+ """
727
+ Decomposes the input cell into its logical components. This method assumes that the positive Z direction is UP.
728
+
729
+ Parameters
730
+ ----------
731
+ cell : topologic.Cell
732
+ the input cell.
733
+ tiltAngle : float , optional
734
+ The threshold tilt angle in degrees to determine if a face is vertical, horizontal, or tilted. The tilt angle is measured from the nearest cardinal direction. The default is 10.
735
+ tolerance : float , optional
736
+ The desired tolerance. The default is 0.0001.
737
+
738
+ Returns
739
+ -------
740
+ dictionary
741
+ A dictionary with the following keys and values:
742
+ 1. "verticalFaces": list of vertical faces
743
+ 2. "topHorizontalFaces": list of top horizontal faces
744
+ 3. "bottomHorizontalFaces": list of bottom horizontal faces
745
+ 4. "inclinedFaces": list of inclined faces
746
+ 5. "verticalApertures": list of vertical apertures
747
+ 6. "topHorizontalApertures": list of top horizontal apertures
748
+ 7. "bottomHorizontalApertures": list of bottom horizontal apertures
749
+ 8. "inclinedApertures": list of inclined apertures
750
+
751
+ """
752
+ from topologicpy.Face import Face
753
+ from topologicpy.Vector import Vector
754
+ from topologicpy.Aperture import Aperture
755
+ from topologicpy.Topology import Topology
756
+ from numpy import arctan, pi, signbit, arctan2, rad2deg
757
+
758
+ def angleCode(f, up, tiltAngle):
759
+ dirA = Face.NormalAtParameters(f)
760
+ ang = round(Vector.Angle(dirA, up), 2)
761
+ if abs(ang - 90) < tiltAngle:
762
+ code = 0
763
+ elif abs(ang) < tiltAngle:
764
+ code = 1
765
+ elif abs(ang - 180) < tiltAngle:
766
+ code = 2
767
+ else:
768
+ code = 3
769
+ return code
770
+
771
+ def getApertures(topology):
772
+ apTopologies = []
773
+ apertures = Topology.Apertures(topology)
774
+ if isinstance(apertures, list):
775
+ for aperture in apertures:
776
+ apTopologies.append(Aperture.Topology(aperture))
777
+ return apTopologies
778
+
779
+ if not isinstance(cell, topologic.Cell):
780
+ return None
781
+ verticalFaces = []
782
+ topHorizontalFaces = []
783
+ bottomHorizontalFaces = []
784
+ inclinedFaces = []
785
+ verticalApertures = []
786
+ topHorizontalApertures = []
787
+ bottomHorizontalApertures = []
788
+ inclinedApertures = []
789
+ tiltAngle = abs(tiltAngle)
790
+ faces = Cell.Faces(cell)
791
+ zList = []
792
+ for f in faces:
793
+ zList.append(f.Centroid().Z())
794
+ zMin = min(zList)
795
+ zMax = max(zList)
796
+ up = [0,0,1]
797
+ for aFace in faces:
798
+ aCode = angleCode(aFace, up, tiltAngle)
799
+
800
+ if aCode == 0:
801
+ verticalFaces.append(aFace)
802
+ verticalApertures += getApertures(aFace)
803
+ elif aCode == 1:
804
+ if abs(aFace.Centroid().Z() - zMin) < tolerance:
805
+ bottomHorizontalFaces.append(aFace)
806
+ bottomHorizontalApertures += getApertures(aFace)
807
+ else:
808
+ topHorizontalFaces.append(aFace)
809
+ topHorizontalApertures += getApertures(aFace)
810
+ elif aCode == 2:
811
+ if abs(aFace.Centroid().Z() - zMax) < tolerance:
812
+ topHorizontalFaces.append(aFace)
813
+ topHorizontalApertures += getApertures(aFace)
814
+ else:
815
+ bottomHorizontalFaces.append(aFace)
816
+ bottomHorizontalApertures += getApertures(aFace)
817
+ elif aCode == 3:
818
+ inclinedFaces.append(aFace)
819
+ inclinedApertures += getApertures(aFace)
820
+ d = {
821
+ "verticalFaces" : verticalFaces,
822
+ "topHorizontalFaces" : topHorizontalFaces,
823
+ "bottomHorizontalFaces" : bottomHorizontalFaces,
824
+ "inclinedFaces" : inclinedFaces,
825
+ "verticalApertures" : verticalApertures,
826
+ "topHorizontalApertures" : topHorizontalApertures,
827
+ "bottomHorizontalApertures" : bottomHorizontalApertures,
828
+ "inclinedApertures" : inclinedApertures
829
+ }
830
+ return d
831
+
832
+ @staticmethod
833
+ def Edges(cell: topologic.Cell) -> list:
834
+ """
835
+ Returns the edges of the input cell.
836
+
837
+ Parameters
838
+ ----------
839
+ cell : topologic.Cell
840
+ The input cell.
841
+
842
+ Returns
843
+ -------
844
+ list
845
+ The list of edges.
846
+
847
+ """
848
+ if not isinstance(cell, topologic.Cell):
849
+ return None
850
+ edges = []
851
+ _ = cell.Edges(None, edges)
852
+ return edges
853
+
854
+ @staticmethod
855
+ def ExternalBoundary(cell: topologic.Cell) -> topologic.Shell:
856
+ """
857
+ Returns the external boundary of the input cell.
858
+
859
+ Parameters
860
+ ----------
861
+ cell : topologic.Cell
862
+ The input cell.
863
+
864
+ Returns
865
+ -------
866
+ topologic.Shell
867
+ The external boundary of the input cell.
868
+
869
+ """
870
+ if not cell:
871
+ return None
872
+ if not isinstance(cell, topologic.Cell):
873
+ return None
874
+ try:
875
+ return cell.ExternalBoundary()
876
+ except:
877
+ return None
878
+
879
+ @staticmethod
880
+ def Faces(cell: topologic.Cell) -> list:
881
+ """
882
+ Returns the faces of the input cell.
883
+
884
+ Parameters
885
+ ----------
886
+ cell : topologic.Cell
887
+ The input cell.
888
+
889
+ Returns
890
+ -------
891
+ list
892
+ The list of faces.
893
+
894
+ """
895
+ if not isinstance(cell, topologic.Cell):
896
+ return None
897
+ faces = []
898
+ _ = cell.Faces(None, faces)
899
+ return faces
900
+
901
+ @staticmethod
902
+ def Hyperboloid(origin: topologic.Cell = None, baseRadius: float = 0.5, topRadius: float = 0.5, height: float = 1, sides: int = 16, direction: list = [0,0,1],
903
+ twist: float = 360, placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
904
+ """
905
+ Creates a hyperboloid.
906
+
907
+ Parameters
908
+ ----------
909
+ origin : topologic.Vertex , optional
910
+ The location of the origin of the hyperboloid. The default is None which results in the hyperboloid being placed at (0,0,0).
911
+ baseRadius : float , optional
912
+ The radius of the base circle of the hyperboloid. The default is 0.5.
913
+ topRadius : float , optional
914
+ The radius of the top circle of the hyperboloid. The default is 0.5.
915
+ height : float , optional
916
+ The height of the cone. The default is 1.
917
+ sides : int , optional
918
+ The number of sides of the cone. The default is 16.
919
+ direction : list , optional
920
+ The vector representing the up direction of the hyperboloid. The default is [0,0,1].
921
+ twist : float , optional
922
+ The angle to twist the base cylinder. The default is 360.
923
+ placement : str , optional
924
+ The description of the placement of the origin of the hyperboloid. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
925
+ tolerance : float , optional
926
+ The desired tolerance. The default is 0.0001.
927
+
928
+ Returns
929
+ -------
930
+ topologic.Cell
931
+ The created hyperboloid.
932
+
933
+ """
934
+
935
+ def createHyperboloid(baseVertices, topVertices, tolerance):
936
+ baseWire = Wire.ByVertices(baseVertices, close=True)
937
+ topWire = Wire.ByVertices(topVertices, close=True)
938
+ baseFace = Face.ByWire(baseWire)
939
+ topFace = Face.ByWire(topWire)
940
+ faces = [baseFace, topFace]
941
+ for i in range(0, len(baseVertices)-1):
942
+ w = Wire.ByVertices([baseVertices[i], topVertices[i], topVertices[i+1]], close=True)
943
+ f = Face.ByWire(w)
944
+ faces.append(f)
945
+ w = Wire.ByVertices([baseVertices[i+1], baseVertices[i], topVertices[i+1]], close=True)
946
+ f = Face.ByWire(w)
947
+ faces.append(f)
948
+ w = Wire.ByVertices([baseVertices[-1], topVertices[-1], topVertices[0]], close=True)
949
+ f = Face.ByWire(w)
950
+ faces.append(f)
951
+ w = Wire.ByVertices([baseVertices[0], baseVertices[-1], topVertices[0]], close=True)
952
+ f = Face.ByWire(w)
953
+ faces.append(f)
954
+ returnTopology = topologic.Cell.ByFaces(faces, tolerance)
955
+ if returnTopology == None:
956
+ returnTopology = topologic.Cluster.ByTopologies(faces)
957
+ return returnTopology
958
+
959
+ from topologicpy.Vertex import Vertex
960
+ from topologicpy.Face import Face
961
+ from topologicpy.Topology import Topology
962
+
963
+ if not origin:
964
+ origin = Vertex.ByCoordinates(0,0,0)
965
+ if not isinstance(origin, topologic.Vertex):
966
+ return None
967
+ baseV = []
968
+ topV = []
969
+ xOffset = 0
970
+ yOffset = 0
971
+ zOffset = 0
972
+ if placement.lower() == "center":
973
+ zOffset = -height*0.5
974
+ elif placement.lower() == "lowerleft":
975
+ xOffset = max(baseRadius, topRadius)
976
+ yOffset = max(baseRadius, topRadius)
977
+ baseZ = origin.Z() + zOffset
978
+ topZ = origin.Z() + zOffset + height
979
+ for i in range(sides):
980
+ angle = math.radians(360/sides)*i
981
+ if baseRadius > 0:
982
+ baseX = math.sin(angle+math.radians(twist))*baseRadius + origin.X() + xOffset
983
+ baseY = math.cos(angle+math.radians(twist))*baseRadius + origin.Y() + yOffset
984
+ baseZ = origin.Z() + zOffset
985
+ baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ))
986
+ if topRadius > 0:
987
+ topX = math.sin(angle-math.radians(twist))*topRadius + origin.X() + xOffset
988
+ topY = math.cos(angle-math.radians(twist))*topRadius + origin.Y() + yOffset
989
+ topV.append(Vertex.ByCoordinates(topX,topY,topZ))
990
+
991
+ hyperboloid = createHyperboloid(baseV, topV, tolerance)
992
+ if hyperboloid == None:
993
+ return None
994
+ x1 = origin.X()
995
+ y1 = origin.Y()
996
+ z1 = origin.Z()
997
+ x2 = origin.X() + direction[0]
998
+ y2 = origin.Y() + direction[1]
999
+ z2 = origin.Z() + direction[2]
1000
+ dx = x2 - x1
1001
+ dy = y2 - y1
1002
+ dz = z2 - z1
1003
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1004
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1005
+ if dist < 0.0001:
1006
+ theta = 0
1007
+ else:
1008
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1009
+ hyperboloid = Topology.Rotate(hyperboloid, origin, 0, 1, 0, theta)
1010
+ hyperboloid = Topology.Rotate(hyperboloid, origin, 0, 0, 1, phi)
1011
+ return hyperboloid
1012
+
1013
+ @staticmethod
1014
+ def InternalBoundaries(cell: topologic.Cell) -> list:
1015
+ """
1016
+ Returns the internal boundaries of the input cell.
1017
+
1018
+ Parameters
1019
+ ----------
1020
+ cell : topologic.Cell
1021
+ The input cell.
1022
+
1023
+ Returns
1024
+ -------
1025
+ list
1026
+ The list of internal boundaries ([topologic.Shell]).
1027
+
1028
+ """
1029
+ shells = []
1030
+ _ = cell.InternalBoundaries(shells)
1031
+ return shells
1032
+
1033
+ @staticmethod
1034
+ def InternalVertex(cell: topologic.Cell, tolerance: float = 0.0001):
1035
+ """
1036
+ Creates a vertex that is guaranteed to be inside the input cell.
1037
+
1038
+ Parameters
1039
+ ----------
1040
+ cell : topologic.Cell
1041
+ The input cell.
1042
+ tolerance : float , optional
1043
+ The desired tolerance. The default is 0.0001.
1044
+
1045
+ Returns
1046
+ -------
1047
+ topologic.Vertex
1048
+ The internal vertex.
1049
+
1050
+ """
1051
+ if not cell:
1052
+ return None
1053
+ if not isinstance(cell, topologic.Cell):
1054
+ return None
1055
+ try:
1056
+ return topologic.CellUtility.InternalVertex(cell, tolerance)
1057
+ except:
1058
+ return None
1059
+
1060
+ @staticmethod
1061
+ def IsInside(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
1062
+ """
1063
+ Returns True if the input vertex is inside the input cell. Returns False otherwise.
1064
+
1065
+ Parameters
1066
+ ----------
1067
+ cell : topologic.Cell
1068
+ The input cell.
1069
+ vertex : topologic.Vertex
1070
+ The input vertex.
1071
+ tolerance : float , optional
1072
+ The desired tolerance. The default is 0.0001.
1073
+
1074
+ Returns
1075
+ -------
1076
+ bool
1077
+ Returns True if the input vertex is inside the input cell. Returns False otherwise.
1078
+
1079
+ """
1080
+ if not isinstance(cell, topologic.Cell):
1081
+ return None
1082
+ try:
1083
+ return (topologic.CellUtility.Contains(cell, vertex, tolerance) == 0)
1084
+ except:
1085
+ return None
1086
+
1087
+ @staticmethod
1088
+ def IsOnBoundary(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
1089
+ """
1090
+ Returns True if the input vertex is on the boundary of the input cell. Returns False otherwise.
1091
+
1092
+ Parameters
1093
+ ----------
1094
+ cell : topologic.Cell
1095
+ The input cell.
1096
+ vertex : topologic.Vertex
1097
+ The input vertex.
1098
+ tolerance : float , optional
1099
+ The desired tolerance. The default is 0.0001.
1100
+
1101
+ Returns
1102
+ -------
1103
+ bool
1104
+ Returns True if the input vertex is inside the input cell. Returns False otherwise.
1105
+
1106
+ """
1107
+ if not cell:
1108
+ return None
1109
+ if not isinstance(cell, topologic.Cell):
1110
+ return None
1111
+ try:
1112
+ return (topologic.CellUtility.Contains(cell, vertex, tolerance) == 1)
1113
+ except:
1114
+ return None
1115
+
1116
+ @staticmethod
1117
+ def IsOutside(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
1118
+ """
1119
+ Returns True if the input vertex is outisde the input cell. Returns False otherwise.
1120
+
1121
+ Parameters
1122
+ ----------
1123
+ cell : topologic.Cell
1124
+ The input cell.
1125
+ vertex : topologic.Vertex
1126
+ The input vertex.
1127
+ tolerance : float , optional
1128
+ The desired tolerance. The default is 0.0001.
1129
+
1130
+ Returns
1131
+ -------
1132
+ bool
1133
+ Returns True if the input vertex is inside the input cell. Returns False otherwise.
1134
+
1135
+ """
1136
+ if not cell:
1137
+ return None
1138
+ if not isinstance(cell, topologic.Cell):
1139
+ return None
1140
+ try:
1141
+ return (topologic.CellUtility.Contains(cell, vertex, tolerance) == 2)
1142
+ except:
1143
+ return None
1144
+
1145
+ @staticmethod
1146
+ def Pipe(edge: topologic.Edge, profile: topologic.Wire = None, radius: float = 0.5, sides: int = 16, startOffset: float = 0, endOffset: float = 0, endcapA: topologic.Topology = None, endcapB: topologic.Topology = None) -> dict:
1147
+ """
1148
+ Description
1149
+ ----------
1150
+ Creates a pipe along the input edge.
1151
+
1152
+ Parameters
1153
+ ----------
1154
+ edge : topologic.Edge
1155
+ The centerline of the pipe.
1156
+ profile : topologic.Wire , optional
1157
+ The profile of the pipe. It is assumed that the profile is in the XY plane. If set to None, a circle of radius 0.5 will be used. The default is None.
1158
+ radius : float , optional
1159
+ The radius of the pipe. The default is 0.5.
1160
+ sides : int , optional
1161
+ The number of sides of the pipe. The default is 16.
1162
+ startOffset : float , optional
1163
+ The offset distance from the start vertex of the centerline edge. The default is 0.
1164
+ endOffset : float , optional
1165
+ The offset distance from the end vertex of the centerline edge. The default is 0.
1166
+ endcapA : topologic.Topology, optional
1167
+ The topology to place at the start vertex of the centerline edge. The positive Z direction of the end cap will be oriented in the direction of the centerline edge.
1168
+ endcapB : topologic.Topology, optional
1169
+ The topology to place at the end vertex of the centerline edge. The positive Z direction of the end cap will be oriented in the inverse direction of the centerline edge.
1170
+
1171
+ Returns
1172
+ -------
1173
+ dict
1174
+ A dictionary containing the pipe, the start endcap, and the end endcap if they have been specified. The dictionary has the following keys:
1175
+ 'pipe'
1176
+ 'endcapA'
1177
+ 'endcapB'
1178
+
1179
+ """
1180
+
1181
+ from topologicpy.Vertex import Vertex
1182
+ from topologicpy.Edge import Edge
1183
+ from topologicpy.Topology import Topology
1184
+
1185
+ if not edge:
1186
+ return None
1187
+ if not isinstance(edge, topologic.Edge):
1188
+ return None
1189
+ length = Edge.Length(edge)
1190
+ origin = Edge.StartVertex(edge)
1191
+ startU = startOffset / length
1192
+ endU = 1.0 - (endOffset / length)
1193
+ sv = Edge.VertexByParameter(edge, startU)
1194
+ ev = Edge.VertexByParameter(edge, endU)
1195
+ x1 = sv.X()
1196
+ y1 = sv.Y()
1197
+ z1 = sv.Z()
1198
+ x2 = ev.X()
1199
+ y2 = ev.Y()
1200
+ z2 = ev.Z()
1201
+ dx = x2 - x1
1202
+ dy = y2 - y1
1203
+ dz = z2 - z1
1204
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1205
+ baseV = []
1206
+ topV = []
1207
+
1208
+ if isinstance(profile, topologic.Wire):
1209
+ baseWire = Topology.Translate(profile, 0 , 0, sv.Z())
1210
+ topWire = Topology.Translate(profile, 0 , 0, sv.Z()+dist)
1211
+ else:
1212
+ for i in range(sides):
1213
+ angle = math.radians(360/sides)*i
1214
+ x = math.sin(angle)*radius + sv.X()
1215
+ y = math.cos(angle)*radius + sv.Y()
1216
+ z = sv.Z()
1217
+ baseV.append(Vertex.ByCoordinates(x,y,z))
1218
+ topV.append(Vertex.ByCoordinates(x,y,z+dist))
1219
+
1220
+ baseWire = Wire.ByVertices(baseV)
1221
+ topWire = Wire.ByVertices(topV)
1222
+ wires = [baseWire, topWire]
1223
+ pipe = Cell.ByWires(wires)
1224
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1225
+ if dist < 0.0001:
1226
+ theta = 0
1227
+ else:
1228
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1229
+ pipe = Topology.Rotate(pipe, sv, 0, 1, 0, theta)
1230
+ pipe = Topology.Rotate(pipe, sv, 0, 0, 1, phi)
1231
+ zzz = Vertex.ByCoordinates(0,0,0)
1232
+ if endcapA:
1233
+ origin = edge.StartVertex()
1234
+ x1 = origin.X()
1235
+ y1 = origin.Y()
1236
+ z1 = origin.Z()
1237
+ x2 = edge.EndVertex().X()
1238
+ y2 = edge.EndVertex().Y()
1239
+ z2 = edge.EndVertex().Z()
1240
+ dx = x2 - x1
1241
+ dy = y2 - y1
1242
+ dz = z2 - z1
1243
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1244
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1245
+ if dist < 0.0001:
1246
+ theta = 0
1247
+ else:
1248
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1249
+ endcapA = Topology.DeepCopy(endcapA)
1250
+ endcapA = Topology.Rotate(endcapA, zzz, 0, 1, 0, theta)
1251
+ endcapA = Topology.Rotate(endcapA, zzz, 0, 0, 1, phi + 180)
1252
+ endcapA = Topology.Translate(endcapA, origin.X(), origin.Y(), origin.Z())
1253
+ if endcapB:
1254
+ origin = edge.EndVertex()
1255
+ x1 = origin.X()
1256
+ y1 = origin.Y()
1257
+ z1 = origin.Z()
1258
+ x2 = edge.StartVertex().X()
1259
+ y2 = edge.StartVertex().Y()
1260
+ z2 = edge.StartVertex().Z()
1261
+ dx = x2 - x1
1262
+ dy = y2 - y1
1263
+ dz = z2 - z1
1264
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1265
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1266
+ if dist < 0.0001:
1267
+ theta = 0
1268
+ else:
1269
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1270
+ endcapB = Topology.DeepCopy(endcapB)
1271
+ endcapB = Topology.Rotate(endcapB, zzz, 0, 1, 0, theta)
1272
+ endcapB = Topology.Rotate(endcapB, zzz, 0, 0, 1, phi + 180)
1273
+ endcapB = Topology.Translate(endcapB, origin.X(), origin.Y(), origin.Z())
1274
+ return {'pipe': pipe, 'endcapA': endcapA, 'endcapB': endcapB}
1275
+
1276
+ @staticmethod
1277
+ def Prism(origin: topologic.Vertex = None, width: float = 1, length: float = 1, height: float = 1, uSides: int = 1, vSides: int = 1, wSides: int = 1,
1278
+ direction: list = [0,0,1], placement: str ="center") -> topologic.Cell:
1279
+ """
1280
+ Description
1281
+ ----------
1282
+ Creates a prism.
1283
+
1284
+ Parameters
1285
+ ----------
1286
+ origin : topologic.Vertex , optional
1287
+ The origin location of the prism. The default is None which results in the prism being placed at (0,0,0).
1288
+ width : float , optional
1289
+ The width of the prism. The default is 1.
1290
+ length : float , optional
1291
+ The length of the prism. The default is 1.
1292
+ height : float , optional
1293
+ The height of the prism.
1294
+ uSides : int , optional
1295
+ The number of sides along the width. The default is 1.
1296
+ vSides : int , optional
1297
+ The number of sides along the length. The default is 1.
1298
+ wSides : int , optional
1299
+ The number of sides along the height. The default is 1.
1300
+ direction : list , optional
1301
+ The vector representing the up direction of the prism. The default is [0,0,1].
1302
+ placement : str , optional
1303
+ The description of the placement of the origin of the prism. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1304
+
1305
+ Returns
1306
+ -------
1307
+ topologic.Cell
1308
+ The created prism.
1309
+
1310
+ """
1311
+ def sliceCell(cell, width, length, height, uSides, vSides, wSides):
1312
+ origin = cell.Centroid()
1313
+ shells = []
1314
+ _ = cell.Shells(None, shells)
1315
+ shell = shells[0]
1316
+ wRect = Wire.Rectangle(origin=origin, width=width*1.2, length=length*1.2, direction=[0, 0, 1], placement="center")
1317
+ sliceFaces = []
1318
+ for i in range(1, wSides):
1319
+ sliceFaces.append(Topology.Translate(Face.ByWire(wRect), 0, 0, height/wSides*i - height*0.5))
1320
+ uRect = Wire.Rectangle(origin=origin, width=height*1.2, length=length*1.2, direction=[1, 0, 0], placement="center")
1321
+ for i in range(1, uSides):
1322
+ sliceFaces.append(Topology.Translate(Face.ByWire(uRect), width/uSides*i - width*0.5, 0, 0))
1323
+ vRect = Wire.Rectangle(origin=origin, width=height*1.2, length=width*1.2, direction=[0, 1, 0], placement="center")
1324
+ for i in range(1, vSides):
1325
+ sliceFaces.append(Topology.Translate(Face.ByWire(vRect), 0, length/vSides*i - length*0.5, 0))
1326
+ if len(sliceFaces) > 0:
1327
+ sliceCluster = topologic.Cluster.ByTopologies(sliceFaces)
1328
+ shell = Topology.Slice(topologyA=shell, topologyB=sliceCluster, tranDict=False)
1329
+ return Cell.ByShell(shell)
1330
+ return cell
1331
+
1332
+ from topologicpy.Vertex import Vertex
1333
+ from topologicpy.Face import Face
1334
+ from topologicpy.Topology import Topology
1335
+
1336
+ if not origin:
1337
+ origin = Vertex.ByCoordinates(0,0,0)
1338
+ if not isinstance(origin, topologic.Vertex):
1339
+ return None
1340
+ xOffset = 0
1341
+ yOffset = 0
1342
+ zOffset = 0
1343
+ if placement.lower() == "center":
1344
+ zOffset = -height*0.5
1345
+ elif placement.lower() == "lowerleft":
1346
+ xOffset = width*0.5
1347
+ yOffset = length*0.5
1348
+ vb1 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset)
1349
+ vb2 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset)
1350
+ vb3 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset)
1351
+ vb4 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset)
1352
+
1353
+ baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
1354
+ baseFace = Face.ByWire(baseWire)
1355
+
1356
+ prism = Cell.ByThickenedFace(baseFace, thickness=height, bothSides = False)
1357
+
1358
+ if uSides > 1 or vSides > 1 or wSides > 1:
1359
+ prism = sliceCell(prism, width, length, height, uSides, vSides, wSides)
1360
+ x1 = origin.X()
1361
+ y1 = origin.Y()
1362
+ z1 = origin.Z()
1363
+ x2 = origin.X() + direction[0]
1364
+ y2 = origin.Y() + direction[1]
1365
+ z2 = origin.Z() + direction[2]
1366
+ dx = x2 - x1
1367
+ dy = y2 - y1
1368
+ dz = z2 - z1
1369
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1370
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1371
+ if dist < 0.0001:
1372
+ theta = 0
1373
+ else:
1374
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1375
+ prism = Topology.Rotate(prism, origin, 0, 1, 0, theta)
1376
+ prism = Topology.Rotate(prism, origin, 0, 0, 1, phi)
1377
+ return prism
1378
+
1379
+ def Roof(face, degree=45, angTolerance=2, tolerance=0.001):
1380
+ """
1381
+ Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
1382
+ This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
1383
+
1384
+ Parameters
1385
+ ----------
1386
+ face : topologic.Face
1387
+ The input face.
1388
+ degree : float , optioal
1389
+ The desired angle in degrees of the roof. The default is 45.
1390
+ angTolerance : float , optional
1391
+ The desired angular tolerance. The default is 2. (This is set to a larger number as it was found to work better)
1392
+ tolerance : float , optional
1393
+ The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
1394
+
1395
+ Returns
1396
+ -------
1397
+ cell
1398
+ The created roof.
1399
+
1400
+ """
1401
+ from topologicpy import Polyskel
1402
+ from topologicpy.Vertex import Vertex
1403
+ from topologicpy.Edge import Edge
1404
+ from topologicpy.Wire import Wire
1405
+ from topologicpy.Face import Face
1406
+ from topologicpy.Shell import Shell
1407
+ from topologicpy.Cell import Cell
1408
+ from topologicpy.Cluster import Cluster
1409
+ from topologicpy.Topology import Topology
1410
+ from topologicpy.Dictionary import Dictionary
1411
+ from topologicpy.Helper import Helper
1412
+ import topologic
1413
+ import math
1414
+ '''
1415
+ def nearest_vertex_2d(v, vertices, tolerance=0.001):
1416
+ for vertex in vertices:
1417
+ x2 = Vertex.X(vertex)
1418
+ y2 = Vertex.Y(vertex)
1419
+ temp_v = Vertex.ByCoordinates(x2, y2, Vertex.Z(v))
1420
+ if Vertex.Distance(v, temp_v) <= tolerance:
1421
+ return vertex
1422
+ return None
1423
+
1424
+ if not isinstance(face, topologic.Face):
1425
+ return None
1426
+ degree = abs(degree)
1427
+ if degree >= 90-tolerance:
1428
+ return None
1429
+ if degree < tolerance:
1430
+ return None
1431
+ flat_face = Face.Flatten(face)
1432
+ d = Topology.Dictionary(flat_face)
1433
+ roof = Wire.Roof(flat_face, degree)
1434
+ if not roof:
1435
+ return None
1436
+ shell = Shell.Skeleton(flat_face)
1437
+ faces = Shell.Faces(shell)
1438
+
1439
+ if not faces:
1440
+ return None
1441
+ triangles = []
1442
+ for face in faces:
1443
+ internalBoundaries = Face.InternalBoundaries(face)
1444
+ if len(internalBoundaries) == 0:
1445
+ if len(Topology.Vertices(face)) > 3:
1446
+ triangles += Face.Triangulate(face)
1447
+ else:
1448
+ triangles += [face]
1449
+
1450
+ roof_vertices = Topology.Vertices(roof)
1451
+ flat_vertices = []
1452
+ for rv in roof_vertices:
1453
+ flat_vertices.append(Vertex.ByCoordinates(Vertex.X(rv), Vertex.Y(rv), 0))
1454
+
1455
+ final_triangles = []
1456
+ for triangle in triangles:
1457
+ if len(Topology.Vertices(triangle)) > 3:
1458
+ triangles = Face.Triangulate(triangle)
1459
+ else:
1460
+ triangles = [triangle]
1461
+ final_triangles += triangles
1462
+
1463
+ final_faces = []
1464
+ for triangle in final_triangles:
1465
+ face_vertices = Topology.Vertices(triangle)
1466
+ top_vertices = []
1467
+ for sv in face_vertices:
1468
+ temp = nearest_vertex_2d(sv, roof_vertices, tolerance=tolerance)
1469
+ if temp:
1470
+ top_vertices.append(temp)
1471
+ else:
1472
+ top_vertices.append(sv)
1473
+ tri_face = Face.ByVertices(top_vertices)
1474
+ final_faces.append(tri_face)
1475
+ '''
1476
+ shell = Shell.Roof(face=face, degree=degree, angTolerance=angTolerance, tolerance=tolerance)
1477
+ faces = Topology.Faces(shell) + [face]
1478
+ cell = Cell.ByFaces(faces, tolerance=tolerance)
1479
+ if not cell:
1480
+ return None
1481
+ return cell
1482
+ '''
1483
+ if not cell:
1484
+ cell = Shell.ByFaces(final_faces, tolerance=tolerance)
1485
+ if not cell:
1486
+ cell = Cluster.ByTopologies(final_faces)
1487
+ cell = Topology.RemoveCoplanarFaces(cell, angTolerance=angTolerance)
1488
+ xTran = Dictionary.ValueAtKey(d,"xTran")
1489
+ yTran = Dictionary.ValueAtKey(d,"yTran")
1490
+ zTran = Dictionary.ValueAtKey(d,"zTran")
1491
+ phi = Dictionary.ValueAtKey(d,"phi")
1492
+ theta = Dictionary.ValueAtKey(d,"theta")
1493
+ cell = Topology.Rotate(cell, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
1494
+ cell = Topology.Rotate(cell, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
1495
+ cell = Topology.Translate(cell, xTran, yTran, zTran)
1496
+ return cell
1497
+ '''
1498
+ @staticmethod
1499
+ def Sets(inputCells: list, superCells: list, tolerance: float = 0.0001) -> list:
1500
+ """
1501
+ Classifies the input cells into sets based on their enclosure within the input list of super cells. The order of the sets follows the order of the input list of super cells.
1502
+
1503
+ Parameters
1504
+ ----------
1505
+ inputCells : list
1506
+ The list of input cells.
1507
+ superCells : list
1508
+ The list of super cells.
1509
+ tolerance : float , optional
1510
+ The desired tolerance. The default is 0.0001.
1511
+
1512
+ Returns
1513
+ -------
1514
+ list
1515
+ The classified list of input cells based on their encolsure within the input list of super cells.
1516
+
1517
+ """
1518
+ if len(superCells) == 0:
1519
+ cluster = inputCells[0]
1520
+ for i in range(1, len(inputCells)):
1521
+ oldCluster = cluster
1522
+ cluster = cluster.Union(inputCells[i])
1523
+ del oldCluster
1524
+ superCells = []
1525
+ _ = cluster.Cells(None, superCells)
1526
+ unused = []
1527
+ for i in range(len(inputCells)):
1528
+ unused.append(True)
1529
+ sets = []
1530
+ for i in range(len(superCells)):
1531
+ sets.append([])
1532
+ for i in range(len(inputCells)):
1533
+ if unused[i]:
1534
+ iv = topologic.CellUtility.InternalVertex(inputCells[i], tolerance)
1535
+ for j in range(len(superCells)):
1536
+ if (topologic.CellUtility.Contains(superCells[j], iv, tolerance) == 0):
1537
+ sets[j].append(inputCells[i])
1538
+ unused[i] = False
1539
+ return sets
1540
+
1541
+ @staticmethod
1542
+ def Shells(cell: topologic.Cell) -> list:
1543
+ """
1544
+ Returns the shells of the input cell.
1545
+
1546
+ Parameters
1547
+ ----------
1548
+ cell : topologic.Cell
1549
+ The input cell.
1550
+
1551
+ Returns
1552
+ -------
1553
+ list
1554
+ The list of shells.
1555
+
1556
+ """
1557
+ if not isinstance(cell, topologic.Cell):
1558
+ return None
1559
+ shells = []
1560
+ _ = cell.Shells(None, shells)
1561
+ return shells
1562
+
1563
+ @staticmethod
1564
+ def Sphere(origin: topologic.Vertex = None, radius: float = 0.5, uSides: int = 16, vSides: int = 8, direction: list = [0,0,1],
1565
+ placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1566
+ """
1567
+ Creates a sphere.
1568
+
1569
+ Parameters
1570
+ ----------
1571
+ origin : topologic.Vertex , optional
1572
+ The origin location of the sphere. The default is None which results in the sphere being placed at (0,0,0).
1573
+ radius : float , optional
1574
+ The radius of the sphere. The default is 0.5.
1575
+ uSides : int , optional
1576
+ The number of sides along the longitude of the sphere. The default is 16.
1577
+ vSides : int , optional
1578
+ The number of sides along the latitude of the sphere. The default is 8.
1579
+ direction : list , optional
1580
+ The vector representing the up direction of the sphere. The default is [0,0,1].
1581
+ placement : str , optional
1582
+ The description of the placement of the origin of the sphere. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1583
+ tolerance : float , optional
1584
+ The desired tolerance. The default is 0.0001.
1585
+
1586
+ Returns
1587
+ -------
1588
+ topologic.Cell
1589
+ The created sphere.
1590
+
1591
+ """
1592
+
1593
+ from topologicpy.Vertex import Vertex
1594
+ from topologicpy.Topology import Topology
1595
+
1596
+ if not origin:
1597
+ origin = Vertex.ByCoordinates(0,0,0)
1598
+ if not isinstance(origin, topologic.Vertex):
1599
+ return None
1600
+ c = Wire.Circle(origin=origin, radius=radius, sides=vSides, fromAngle=90, toAngle=270, close=False, direction=[0, 1, 0], placement="center")
1601
+ s = Topology.Spin(c, origin=origin, triangulate=False, direction=[0,0,1], degree=360, sides=uSides, tolerance=tolerance)
1602
+ if s.Type() == topologic.CellComplex.Type():
1603
+ s = s.ExternalBoundary()
1604
+ if s.Type() == topologic.Shell.Type():
1605
+ s = topologic.Cell.ByShell(s)
1606
+ if placement.lower() == "bottom":
1607
+ s = Topology.Translate(s, 0, 0, radius)
1608
+ elif placement.lower() == "lowerleft":
1609
+ s = Topology.Translate(s, radius, radius, radius)
1610
+ x1 = origin.X()
1611
+ y1 = origin.Y()
1612
+ z1 = origin.Z()
1613
+ x2 = origin.X() + direction[0]
1614
+ y2 = origin.Y() + direction[1]
1615
+ z2 = origin.Z() + direction[2]
1616
+ dx = x2 - x1
1617
+ dy = y2 - y1
1618
+ dz = z2 - z1
1619
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1620
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1621
+ if dist < 0.0001:
1622
+ theta = 0
1623
+ else:
1624
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1625
+ s = Topology.Rotate(s, origin, 0, 1, 0, theta)
1626
+ s = Topology.Rotate(s, origin, 0, 0, 1, phi)
1627
+ return s
1628
+
1629
+ @staticmethod
1630
+ def SurfaceArea(cell: topologic.Cell, mantissa: int = 4) -> float:
1631
+ """
1632
+ Returns the surface area of the input cell.
1633
+
1634
+ Parameters
1635
+ ----------
1636
+ cell : topologic.Cell
1637
+ The cell.
1638
+ mantissa : int , optional
1639
+ The desired length of the mantissa. The default is 4.
1640
+
1641
+ Returns
1642
+ -------
1643
+ area : float
1644
+ The surface area of the input cell.
1645
+
1646
+ """
1647
+ return Cell.Area(cell=cell, mantissa=mantissa)
1648
+
1649
+ @staticmethod
1650
+ def Torus(origin: topologic.Vertex = None, majorRadius: float = 0.5, minorRadius: float = 0.125, uSides: int = 16, vSides: int = 8, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1651
+ """
1652
+ Creates a torus.
1653
+
1654
+ Parameters
1655
+ ----------
1656
+ origin : topologic.Vertex , optional
1657
+ The origin location of the torus. The default is None which results in the torus being placed at (0,0,0).
1658
+ majorRadius : float , optional
1659
+ The major radius of the torus. The default is 0.5.
1660
+ minorRadius : float , optional
1661
+ The minor radius of the torus. The default is 0.1.
1662
+ uSides : int , optional
1663
+ The number of sides along the longitude of the torus. The default is 16.
1664
+ vSides : int , optional
1665
+ The number of sides along the latitude of the torus. The default is 8.
1666
+ direction : list , optional
1667
+ The vector representing the up direction of the torus. The default is [0,0,1].
1668
+ placement : str , optional
1669
+ The description of the placement of the origin of the torus. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1670
+ tolerance : float , optional
1671
+ The desired tolerance. The default is 0.0001.
1672
+
1673
+ Returns
1674
+ -------
1675
+ topologic.Cell
1676
+ The created torus.
1677
+
1678
+ """
1679
+
1680
+ from topologicpy.Vertex import Vertex
1681
+ from topologicpy.Topology import Topology
1682
+
1683
+ if not origin:
1684
+ origin = Vertex.ByCoordinates(0,0,0)
1685
+ if not isinstance(origin, topologic.Vertex):
1686
+ return None
1687
+ c = Wire.Circle(origin=origin, radius=minorRadius, sides=vSides, fromAngle=0, toAngle=360, close=False, direction=[0, 1, 0], placement="center")
1688
+ c = Topology.Translate(c, abs(majorRadius-minorRadius), 0, 0)
1689
+ s = Topology.Spin(c, origin=origin, triangulate=False, direction=[0,0,1], degree=360, sides=uSides, tolerance=tolerance)
1690
+ if s.Type() == topologic.Shell.Type():
1691
+ s = topologic.Cell.ByShell(s)
1692
+ if placement.lower() == "bottom":
1693
+ s = Topology.Translate(s, 0, 0, majorRadius)
1694
+ elif placement.lower() == "lowerleft":
1695
+ s = Topology.Translate(s, majorRadius, majorRadius, minorRadius)
1696
+ x1 = origin.X()
1697
+ y1 = origin.Y()
1698
+ z1 = origin.Z()
1699
+ x2 = origin.X() + direction[0]
1700
+ y2 = origin.Y() + direction[1]
1701
+ z2 = origin.Z() + direction[2]
1702
+ dx = x2 - x1
1703
+ dy = y2 - y1
1704
+ dz = z2 - z1
1705
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1706
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1707
+ if dist < 0.0001:
1708
+ theta = 0
1709
+ else:
1710
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1711
+ s = Topology.Rotate(s, origin, 0, 1, 0, theta)
1712
+ s = Topology.Rotate(s, origin, 0, 0, 1, phi)
1713
+ return s
1714
+
1715
+ @staticmethod
1716
+ def Vertices(cell: topologic.Cell) -> list:
1717
+ """
1718
+ Returns the vertices of the input cell.
1719
+
1720
+ Parameters
1721
+ ----------
1722
+ cell : topologic.Cell
1723
+ The input cell.
1724
+
1725
+ Returns
1726
+ -------
1727
+ list
1728
+ The list of vertices.
1729
+
1730
+ """
1731
+ if not isinstance(cell, topologic.Cell):
1732
+ return None
1733
+ vertices = []
1734
+ _ = cell.Vertices(None, vertices)
1735
+ return vertices
1736
+
1737
+ @staticmethod
1738
+ def Volume(cell: topologic.Cell, mantissa: int = 4) -> float:
1739
+ """
1740
+ Returns the volume of the input cell.
1741
+
1742
+ Parameters
1743
+ ----------
1744
+ cell : topologic.Cell
1745
+ The input cell.
1746
+ manitssa: int , optional
1747
+ The desired length of the mantissa. The default is 4.
1748
+
1749
+ Returns
1750
+ -------
1751
+ float
1752
+ The volume of the input cell.
1753
+
1754
+ """
1755
+ if not cell:
1756
+ return None
1757
+ return round(topologic.CellUtility.Volume(cell), mantissa)
1758
+
1759
+ @staticmethod
1760
+ def Wires(cell: topologic.Cell) -> list:
1761
+ """
1762
+ Returns the wires of the input cell.
1763
+
1764
+ Parameters
1765
+ ----------
1766
+ cell : topologic.Cell
1767
+ The input cell.
1768
+
1769
+ Returns
1770
+ -------
1771
+ list
1772
+ The list of wires.
1773
+
1774
+ """
1775
+ if not isinstance(cell, topologic.Cell):
1776
+ return None
1777
+ wires = []
1778
+ _ = cell.Wires(None, wires)
1779
+ return wires
1780
+