topologicpy 0.5.8__py3-none-any.whl → 6.0.0__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 (94) hide show
  1. topologicpy/Aperture.py +72 -72
  2. topologicpy/Cell.py +2169 -2169
  3. topologicpy/CellComplex.py +1137 -1137
  4. topologicpy/Cluster.py +1288 -1280
  5. topologicpy/Color.py +423 -393
  6. topologicpy/Context.py +79 -79
  7. topologicpy/DGL.py +3213 -3136
  8. topologicpy/Dictionary.py +698 -695
  9. topologicpy/Edge.py +1187 -1187
  10. topologicpy/EnergyModel.py +1180 -1171
  11. topologicpy/Face.py +2141 -2141
  12. topologicpy/Graph.py +7768 -7700
  13. topologicpy/Grid.py +353 -353
  14. topologicpy/Helper.py +507 -507
  15. topologicpy/Honeybee.py +461 -461
  16. topologicpy/Matrix.py +271 -271
  17. topologicpy/Neo4j.py +521 -521
  18. topologicpy/Plotly.py +2 -2
  19. topologicpy/Polyskel.py +541 -541
  20. topologicpy/Shell.py +1768 -1768
  21. topologicpy/Speckle.py +508 -508
  22. topologicpy/Topology.py +7060 -6988
  23. topologicpy/Vector.py +905 -905
  24. topologicpy/Vertex.py +1585 -1585
  25. topologicpy/Wire.py +3050 -3050
  26. topologicpy/__init__.py +22 -38
  27. topologicpy/version.py +1 -0
  28. {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/LICENSE +661 -704
  29. topologicpy-6.0.0.dist-info/METADATA +751 -0
  30. topologicpy-6.0.0.dist-info/RECORD +32 -0
  31. topologicpy/bin/linux/topologic/__init__.py +0 -2
  32. topologicpy/bin/linux/topologic/libTKBO-6bdf205d.so.7.7.0 +0 -0
  33. topologicpy/bin/linux/topologic/libTKBRep-2960a069.so.7.7.0 +0 -0
  34. topologicpy/bin/linux/topologic/libTKBool-c44b74bd.so.7.7.0 +0 -0
  35. topologicpy/bin/linux/topologic/libTKFillet-9a670ba0.so.7.7.0 +0 -0
  36. topologicpy/bin/linux/topologic/libTKG2d-8f31849e.so.7.7.0 +0 -0
  37. topologicpy/bin/linux/topologic/libTKG3d-4c6bce57.so.7.7.0 +0 -0
  38. topologicpy/bin/linux/topologic/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
  39. topologicpy/bin/linux/topologic/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
  40. topologicpy/bin/linux/topologic/libTKMath-72572fa8.so.7.7.0 +0 -0
  41. topologicpy/bin/linux/topologic/libTKMesh-2a060427.so.7.7.0 +0 -0
  42. topologicpy/bin/linux/topologic/libTKOffset-6cab68ff.so.7.7.0 +0 -0
  43. topologicpy/bin/linux/topologic/libTKPrim-eb1262b3.so.7.7.0 +0 -0
  44. topologicpy/bin/linux/topologic/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
  45. topologicpy/bin/linux/topologic/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
  46. topologicpy/bin/linux/topologic/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
  47. topologicpy/bin/linux/topologic/libgcc_s-32c1665e.so.1 +0 -0
  48. topologicpy/bin/linux/topologic/libstdc++-672d7b41.so.6.0.30 +0 -0
  49. topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
  50. topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
  51. topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
  52. topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
  53. topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
  54. topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
  55. topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
  56. topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
  57. topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
  58. topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
  59. topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
  60. topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
  61. topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
  62. topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
  63. topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
  64. topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
  65. topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
  66. topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
  67. topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
  68. topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
  69. topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
  70. topologicpy/bin/macos/topologic/__init__.py +0 -2
  71. topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
  72. topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
  73. topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
  74. topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
  75. topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
  76. topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
  77. topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
  78. topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
  79. topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
  80. topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
  81. topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
  82. topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
  83. topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
  84. topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
  85. topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
  86. topologicpy/bin/windows/topologic/__init__.py +0 -2
  87. topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
  88. topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
  89. topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
  90. topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
  91. topologicpy-0.5.8.dist-info/METADATA +0 -96
  92. topologicpy-0.5.8.dist-info/RECORD +0 -91
  93. {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/WHEEL +0 -0
  94. {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/top_level.txt +0 -0
topologicpy/Cell.py CHANGED
@@ -1,2169 +1,2169 @@
1
- # Copyright (C) 2024
2
- # Wassim Jabi <wassim.jabi@gmail.com>
3
- #
4
- # This program is free software: you can redistribute it and/or modify it under
5
- # the terms of the GNU Affero General Public License as published by the Free Software
6
- # Foundation, either version 3 of the License, or (at your option) any later
7
- # version.
8
- #
9
- # This program is distributed in the hope that it will be useful, but WITHOUT
10
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
- # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12
- # details.
13
- #
14
- # You should have received a copy of the GNU Affero General Public License along with
15
- # this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- import topologic
18
- from topologicpy.Wire import Wire
19
- from topologicpy.Topology import Topology
20
- import math
21
-
22
- class Cell(Topology):
23
- @staticmethod
24
- def Area(cell: topologic.Cell, mantissa: int = 6) -> float:
25
- """
26
- Returns the surface area of the input cell.
27
-
28
- Parameters
29
- ----------
30
- cell : topologic.Cell
31
- The cell.
32
- mantissa : int , optional
33
- The desired length of the mantissa. The default is 6.
34
-
35
- Returns
36
- -------
37
- float
38
- The surface area of the input cell.
39
-
40
- """
41
- from topologicpy.Face import Face
42
-
43
- faces = []
44
- _ = cell.Faces(None, faces)
45
- area = 0.0
46
- for aFace in faces:
47
- area = area + Face.Area(aFace)
48
- return round(area, mantissa)
49
-
50
- @staticmethod
51
- def Box(origin: topologic.Vertex = None,
52
- width: float = 1, length: float = 1, height: float = 1,
53
- uSides: int = 1, vSides: int = 1, wSides: int = 1,
54
- direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
55
- """
56
- Creates a box.
57
-
58
- Parameters
59
- ----------
60
- origin : topologic.Vertex , optional
61
- The origin location of the box. The default is None which results in the box being placed at (0, 0, 0).
62
- width : float , optional
63
- The width of the box. The default is 1.
64
- length : float , optional
65
- The length of the box. The default is 1.
66
- height : float , optional
67
- The height of the box.
68
- uSides : int , optional
69
- The number of sides along the width. The default is 1.
70
- vSides : int , optional
71
- The number of sides along the length. The default is 1.
72
- wSides : int , optional
73
- The number of sides along the height. The default is 1.
74
- direction : list , optional
75
- The vector representing the up direction of the box. The default is [0, 0, 1].
76
- placement : str , optional
77
- 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".
78
- tolerance : float , optional
79
- The desired tolerance. The default is 0.0001.
80
-
81
- Returns
82
- -------
83
- topologic.Cell
84
- The created box.
85
-
86
- """
87
- return Cell.Prism(origin=origin, width=width, length=length, height=height,
88
- uSides=uSides, vSides=vSides, wSides=wSides,
89
- direction=direction, placement=placement, tolerance=tolerance)
90
-
91
- @staticmethod
92
- def ByFaces(faces: list, planarize: bool = False, tolerance: float = 0.0001, silent=False) -> topologic.Cell:
93
- """
94
- Creates a cell from the input list of faces.
95
-
96
- Parameters
97
- ----------
98
- faces : list
99
- The input list of faces.
100
- planarize : bool, optional
101
- If set to True, the input faces are planarized before building the cell. Otherwise, they are not. The default is False.
102
- tolerance : float , optional
103
- The desired tolerance. The default is 0.0001.
104
- silent : bool , optional
105
- If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
106
-
107
- Returns
108
- -------
109
- topologic.Cell
110
- The created cell.
111
-
112
- """
113
- from topologicpy.Vertex import Vertex
114
- from topologicpy.Wire import Wire
115
- from topologicpy.Face import Face
116
- from topologicpy.Topology import Topology
117
-
118
- if not isinstance(faces, list):
119
- if not silent:
120
- print("Cell.ByFaces - Error: The input faces parameter is not a valid list. Returning None.")
121
- return None
122
- faceList = [x for x in faces if isinstance(x, topologic.Face)]
123
- if len(faceList) < 1:
124
- if not silent:
125
- print("Cell.ByFaces - Error: The input faces parameter does not contain valid faces. Returning None.")
126
- return None
127
- # Try the default method
128
- cell = topologic.Cell.ByFaces(faceList, tolerance)
129
- if isinstance(cell, topologic.Cell):
130
- return cell
131
-
132
- # Fuse all the vertices first and rebuild the faces
133
- all_vertices = []
134
- wires = []
135
- for f in faceList:
136
- w = Face.Wire(f)
137
- wires.append(w)
138
- all_vertices += Topology.Vertices(w)
139
- all_vertices = Vertex.Fuse(all_vertices, tolerance=tolerance)
140
- new_faces = []
141
- for w in wires:
142
- face_vertices = []
143
- for v in Topology.Vertices(w):
144
- index = Vertex.Index(v, all_vertices, tolerance=tolerance)
145
- if not index == None:
146
- face_vertices.append(all_vertices[index])
147
- new_w = Wire.ByVertices(face_vertices)
148
- if isinstance(new_w, topologic.Wire):
149
- new_f = Face.ByWire(new_w, silent=True)
150
- if isinstance(new_f, topologic.Face):
151
- new_faces.append(new_f)
152
- elif isinstance(new_f, list):
153
- new_faces += new_f
154
- faceList = new_faces
155
- planarizedList = []
156
- enlargedList = []
157
- if planarize:
158
- planarizedList = [Face.Planarize(f, tolerance=tolerance) for f in faceList]
159
- enlargedList = [Face.ByOffset(f, offset=-tolerance*10) for f in planarizedList]
160
- cell = topologic.Cell.ByFaces(enlargedList, tolerance)
161
- faceList = Topology.SubTopologies(cell, subTopologyType="face")
162
- finalFaces = []
163
- for f in faceList:
164
- centroid = Topology.Centroid(f)
165
- n = Face.Normal(f)
166
- v = Topology.Translate(centroid, n[0]*0.01, n[1]*0.01, n[2]*0.01)
167
- if not Vertex.IsInternal(v, cell):
168
- finalFaces.append(f)
169
- finalFinalFaces = []
170
- for f in finalFaces:
171
- vertices = Face.Vertices(f)
172
- w = Wire.Cycles(Face.ExternalBoundary(f), maxVertices=len(vertices))[0]
173
- f1 = Face.ByWire(w, tolerance=tolerance, silent=True)
174
- if isinstance(f1, topologic.Face):
175
- finalFinalFaces.append(f1)
176
- elif isinstance(f1, list):
177
- finalFinalFaces += f1
178
- cell = topologic.Cell.ByFaces(finalFinalFaces, tolerance)
179
- if cell == None:
180
- if not silent:
181
- print("Cell.ByFaces - Error: The operation failed. Returning None.")
182
- return None
183
- else:
184
- return cell
185
- else:
186
- cell = topologic.Cell.ByFaces(faces, tolerance)
187
- if cell == None:
188
- if not silent:
189
- print("Cell.ByFaces - Error: The operation failed. Returning None.")
190
- return None
191
- else:
192
- return cell
193
- @staticmethod
194
- def ByOffset(cell: topologic.Cell, offset: float = 1.0, tolerance: float = 0.0001) -> topologic.Face:
195
- """
196
- Creates an offset cell from the input cell.
197
-
198
- Parameters
199
- ----------
200
- cell : topologic.Cell
201
- The input cell.
202
- offset : float , optional
203
- The desired offset distance. The default is 1.0.
204
- tolerance : float , optional
205
- The desired tolerance. The default is 0.0001.
206
-
207
- Returns
208
- -------
209
- topologic.Topology
210
- The created offset topology. WARNING: This method may fail to create a cell if the offset creates self-intersecting faces. Always check the type being returned by this method.
211
-
212
- """
213
- from topologicpy.Face import Face
214
- from topologicpy.Topology import Topology
215
- from topologicpy.Vector import Vector
216
-
217
- vertices = Topology.Vertices(cell)
218
- new_vertices = []
219
- for v in vertices:
220
- faces = Topology.SuperTopologies(v, hostTopology=cell, topologyType="face")
221
- normals = []
222
- for face in faces:
223
- normal = Vector.SetMagnitude(Face.Normal(face), offset)
224
- normals.append(normal)
225
- sum_normal = Vector.Sum(normals)
226
- new_v = Topology.TranslateByDirectionDistance(v, direction=sum_normal, distance=Vector.Magnitude(sum_normal))
227
- new_vertices.append(new_v)
228
- new_cell = Topology.SelfMerge(Topology.ReplaceVertices(cell, Topology.Vertices(cell), new_vertices), tolerance=tolerance)
229
- return new_cell
230
-
231
- @staticmethod
232
- def ByShell(shell: topologic.Shell, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
233
- """
234
- Creates a cell from the input shell.
235
-
236
- Parameters
237
- ----------
238
- shell : topologic.Shell
239
- The input shell. The shell must be closed for this method to succeed.
240
- planarize : bool, optional
241
- 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.
242
- tolerance : float , optional
243
- The desired tolerance. The default is 0.0001.
244
-
245
- Returns
246
- -------
247
- topologic.Cell
248
- The created cell.
249
-
250
- """
251
- from topologicpy.Topology import Topology
252
- if not isinstance(shell, topologic.Shell):
253
- print("Cell.ByShell - Error: The input shell parameter is not a valid topologic shell. Returning None.")
254
- return None
255
- faces = Topology.SubTopologies(shell, subTopologyType="face")
256
- return Cell.ByFaces(faces, planarize=planarize, tolerance=tolerance)
257
-
258
- @staticmethod
259
- def ByThickenedFace(face: topologic.Face, thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
260
- planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
261
- """
262
- Creates a cell by thickening the input face.
263
-
264
- Parameters
265
- ----------
266
- face : topologic.Face
267
- The input face to be thickened.
268
- thickness : float , optional
269
- The desired thickness. The default is 1.0.
270
- bothSides : bool
271
- 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.
272
- reverse : bool
273
- If True, the cell will be lofted in the opposite direction of the normal to the face. The default is False.
274
- planarize : bool, optional
275
- 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.
276
- tolerance : float , optional
277
- The desired tolerance. The default is 0.0001.
278
-
279
- Returns
280
- -------
281
- topologic.Cell
282
- The created cell.
283
-
284
- """
285
- from topologicpy.Edge import Edge
286
- from topologicpy.Face import Face
287
- from topologicpy.Cluster import Cluster
288
- from topologicpy.Topology import Topology
289
-
290
- if not isinstance(face, topologic.Face):
291
- print("Cell.ByThickenedFace - Error: The input face parameter is not a valid topologic face. Returning None.")
292
- return None
293
- if reverse == True and bothSides == False:
294
- thickness = -thickness
295
- faceNormal = Face.Normal(face)
296
- if bothSides:
297
- bottomFace = Topology.Translate(face, -faceNormal[0]*0.5*thickness, -faceNormal[1]*0.5*thickness, -faceNormal[2]*0.5*thickness)
298
- topFace = Topology.Translate(face, faceNormal[0]*0.5*thickness, faceNormal[1]*0.5*thickness, faceNormal[2]*0.5*thickness)
299
- else:
300
- bottomFace = face
301
- topFace = Topology.Translate(face, faceNormal[0]*thickness, faceNormal[1]*thickness, faceNormal[2]*thickness)
302
-
303
- cellFaces = [bottomFace, topFace]
304
- bottomEdges = []
305
- _ = bottomFace.Edges(None, bottomEdges)
306
- for bottomEdge in bottomEdges:
307
- topEdge = Topology.Translate(bottomEdge, faceNormal[0]*thickness, faceNormal[1]*thickness, faceNormal[2]*thickness)
308
- sideEdge1 = Edge.ByVertices([bottomEdge.StartVertex(), topEdge.StartVertex()], tolerance=tolerance, silent=True)
309
- sideEdge2 = Edge.ByVertices([bottomEdge.EndVertex(), topEdge.EndVertex()], tolerance=tolerance, silent=True)
310
- cellWire = Topology.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]), tolerance=tolerance)
311
- cellFaces.append(Face.ByWire(cellWire, tolerance=tolerance))
312
- return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
313
-
314
- @staticmethod
315
- def ByThickenedShell(shell: topologic.Shell, direction: list = [0, 0, 1], thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
316
- planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
317
- """
318
- Creates a cell by thickening the input shell. The shell must be open.
319
-
320
- Parameters
321
- ----------
322
- shell : topologic.Shell
323
- The input shell to be thickened.
324
- thickness : float , optional
325
- The desired thickness. The default is 1.0.
326
- bothSides : bool
327
- 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.
328
- reverse : bool
329
- If True, the cell will be lofted along the opposite of the input direction. The default is False.
330
- planarize : bool, optional
331
- 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.
332
- tolerance : float , optional
333
- The desired tolerance. The default is 0.0001.
334
-
335
- Returns
336
- -------
337
- topologic.Cell
338
- The created cell.
339
-
340
- """
341
- from topologicpy.Edge import Edge
342
- from topologicpy.Wire import Wire
343
- from topologicpy.Face import Face
344
- from topologicpy.Shell import Shell
345
- from topologicpy.Cluster import Cluster
346
- from topologicpy.Topology import Topology
347
- if not isinstance(shell, topologic.Shell):
348
- print("Cell.ByThickenedShell - Error: The input shell parameter is not a valid topologic Shell. Returning None.")
349
- return None
350
- if reverse == True and bothSides == False:
351
- thickness = -thickness
352
- if bothSides:
353
- bottomShell = Topology.Translate(shell, -direction[0]*0.5*thickness, -direction[1]*0.5*thickness, -direction[2]*0.5*thickness)
354
- topShell = Topology.Translate(shell, direction[0]*0.5*thickness, direction[1]*0.5*thickness, direction[2]*0.5*thickness)
355
- else:
356
- bottomShell = shell
357
- topShell = Topology.Translate(shell, direction[0]*thickness, direction[1]*thickness, direction[2]*thickness)
358
- cellFaces = Shell.Faces(bottomShell) + Shell.Faces(topShell)
359
- bottomWire = Shell.ExternalBoundary(bottomShell, tolerance=tolerance)
360
- bottomEdges = Wire.Edges(bottomWire)
361
- for bottomEdge in bottomEdges:
362
- topEdge = Topology.Translate(bottomEdge, direction[0]*thickness, direction[1]*thickness, direction[2]*thickness)
363
- sideEdge1 = Edge.ByVertices([Edge.StartVertex(bottomEdge), Edge.StartVertex(topEdge)], tolerance=tolerance, silent=True)
364
- sideEdge2 = Edge.ByVertices([Edge.EndVertex(bottomEdge), Edge.EndVertex(topEdge)], tolerance=tolerance, silent=True)
365
- cellWire = Topology.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]), tolerance=tolerance)
366
- cellFace = Face.ByWire(cellWire, tolerance=tolerance)
367
- cellFaces.append(cellFace)
368
- return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
369
-
370
- @staticmethod
371
- def ByWires(wires: list, close: bool = False, triangulate: bool = True, planarize: bool = False, mantissa: int = 6, tolerance: float = 0.0001, silent=False) -> topologic.Cell:
372
- """
373
- Creates a cell by lofting through the input list of wires.
374
-
375
- Parameters
376
- ----------
377
- wires : topologic.Wire
378
- The input list of wires.
379
- close : bool , optional
380
- 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.
381
- triangulate : bool , optional
382
- If set to True, the faces will be triangulated. The default is True.
383
- mantissa : int , optional
384
- The desired length of the mantissa. The default is 6.
385
- tolerance : float , optional
386
- The desired tolerance. The default is 0.0001.
387
- silent : bool , optional
388
- If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
389
-
390
- Raises
391
- ------
392
- Exception
393
- Raises an exception if the two wires in the list do not have the same number of edges.
394
-
395
- Returns
396
- -------
397
- topologic.Cell
398
- The created cell.
399
-
400
- """
401
- from topologicpy.Edge import Edge
402
- from topologicpy.Wire import Wire
403
- from topologicpy.Face import Face
404
- from topologicpy.Shell import Shell
405
- from topologicpy.Topology import Topology
406
-
407
- if not isinstance(wires, list):
408
- if not silent:
409
- print("Cell.ByWires - Error: The input wires parameter is not a valid list. Returning None.")
410
- return None
411
- wires = [w for w in wires if isinstance(w, topologic.Wire)]
412
- if len(wires) < 2:
413
- if not silent:
414
- print("Cell.ByWires - Error: The input wires parameter contains less than two valid topologic wires. Returning None.")
415
- return None
416
- faces = [Face.ByWire(wires[0], tolerance=tolerance), Face.ByWire(wires[-1], tolerance=tolerance)]
417
- if close == True:
418
- faces.append(Face.ByWire(wires[0], tolerance=tolerance))
419
- if triangulate == True:
420
- triangles = []
421
- for face in faces:
422
- if len(Topology.Vertices(face)) > 3:
423
- triangles += Face.Triangulate(face, tolerance=tolerance)
424
- else:
425
- triangles += [face]
426
- faces = triangles
427
- for i in range(len(wires)-1):
428
- wire1 = wires[i]
429
- wire2 = wires[i+1]
430
- w1_edges = []
431
- _ = wire1.Edges(None, w1_edges)
432
- w2_edges = []
433
- _ = wire2.Edges(None, w2_edges)
434
- if len(w1_edges) != len(w2_edges):
435
- if not silent:
436
- print("Cell.ByWires - Error: The input wires parameter contains wires with different number of edges. Returning None.")
437
- return None
438
- if triangulate == True:
439
- for j in range (len(w1_edges)):
440
- e1 = w1_edges[j]
441
- e2 = w2_edges[j]
442
- e3 = None
443
- e4 = None
444
- try:
445
- e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
446
- except:
447
- try:
448
- e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
449
- faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e4], tolerance=tolerance), tolerance=tolerance))
450
- except:
451
- pass
452
- try:
453
- e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
454
- except:
455
- try:
456
- e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
457
- faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e3], tolerance=tolerance), tolerance=tolerance))
458
- except:
459
- pass
460
- if e3 and e4:
461
- e5 = Edge.ByVertices([e1.StartVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
462
- faces.append(Face.ByWire(Wire.ByEdges([e1, e5, e4], tolerance=tolerance), tolerance=tolerance))
463
- faces.append(Face.ByWire(Wire.ByEdges([e2, e5, e3], tolerance=tolerance), tolerance=tolerance))
464
- else:
465
- for j in range (len(w1_edges)):
466
- e1 = w1_edges[j]
467
- e2 = w2_edges[j]
468
- e3 = None
469
- e4 = None
470
- try:
471
- e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
472
- except:
473
- try:
474
- e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
475
- except:
476
- pass
477
- try:
478
- e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
479
- except:
480
- try:
481
- e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
482
- except:
483
- pass
484
- if e3 and e4:
485
- try:
486
- faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2, e3], tolerance=tolerance), tolerance=tolerance))
487
- except:
488
- faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2, e4], tolerance=tolerance), tolerance=tolerance))
489
- elif e3:
490
- faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2], tolerance=tolerance), tolerance=tolerance))
491
- elif e4:
492
- faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2], tolerance=tolerance), tolerance=tolerance))
493
- cell = Cell.ByFaces(faces, planarize=planarize, tolerance=tolerance, silent=silent)
494
- if not cell:
495
- shell = Shell.ByFaces(faces, tolerance=tolerance)
496
- if isinstance(shell, topologic.Shell):
497
- geom = Topology.Geometry(shell, mantissa=mantissa)
498
- cell = Topology.ByGeometry(geom['vertices'], geom['edges'], geom['faces'])
499
- if not isinstance(cell, topologic.Cell):
500
- print("Cell.ByWires - Error: Could not create a cell. Returning None.")
501
- return None
502
- return cell
503
-
504
- @staticmethod
505
- def ByWiresCluster(cluster: topologic.Cluster, close: bool = False, triangulate: bool = True, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
506
- """
507
- Creates a cell by lofting through the input cluster of wires.
508
-
509
- Parameters
510
- ----------
511
- cluster : topologic.Cluster
512
- The input Cluster of wires.
513
- close : bool , optional
514
- 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.
515
- triangulate : bool , optional
516
- If set to True, the faces will be triangulated. The default is True.
517
- tolerance : float , optional
518
- The desired tolerance. The default is 0.0001.
519
-
520
- Raises
521
- ------
522
- Exception
523
- Raises an exception if the two wires in the list do not have the same number of edges.
524
-
525
- Returns
526
- -------
527
- topologic.Cell
528
- The created cell.
529
-
530
- """
531
- if not isinstance(cluster, topologic.Cluster):
532
- print("Cell.ByWiresCluster - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
533
- return None
534
- wires = []
535
- _ = cluster.Wires(None, wires)
536
- return Cell.ByWires(wires, close=close, triangulate=triangulate, planarize=planarize, tolerance=tolerance)
537
-
538
- @staticmethod
539
- def Capsule(origin: topologic.Vertex = None, radius: float = 0.25, height: float = 1, uSides: int = 16, vSidesEnds:int = 8, vSidesMiddle: int = 1, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
540
- """
541
- Creates a capsule shape. A capsule is a cylinder with hemispherical ends.
542
-
543
- Parameters
544
- ----------
545
- origin : topologic.Vertex , optional
546
- The location of the origin of the cylinder. The default is None which results in the cylinder being placed at (0, 0, 0).
547
- radius : float , optional
548
- The radius of the capsule. The default is 0.25.
549
- height : float , optional
550
- The height of the capsule. The default is 1.
551
- uSides : int , optional
552
- The number of circle segments of the capsule. The default is 16.
553
- vSidesEnds : int , optional
554
- The number of vertical segments of the end hemispheres. The default is 8.
555
- vSidesMiddle : int , optional
556
- The number of vertical segments of the middle cylinder. The default is 1.
557
- direction : list , optional
558
- The vector representing the up direction of the capsule. The default is [0, 0, 1].
559
- placement : str , optional
560
- The description of the placement of the origin of the capsule. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "bottom".
561
- tolerance : float , optional
562
- The desired tolerance. The default is 0.0001.
563
-
564
- Returns
565
- -------
566
- topologic.Cell
567
- The created cell.
568
-
569
- """
570
- from topologicpy.Topology import Topology
571
- from topologicpy.Cell import Cell
572
- from topologicpy.Vertex import Vertex
573
- if not origin:
574
- origin = Vertex.ByCoordinates(0, 0, 0)
575
- if not isinstance(origin, topologic.Vertex):
576
- print("Cell.Capsule - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
577
- return None
578
- cyl_height = height - radius*2
579
- if cyl_height <= 0:
580
- capsule = Cell.Sphere(origin=Vertex.Origin(), radius=radius, uSides= uSides, vSides=vSidesEnds*2)
581
- else:
582
- cyl = Cell.Cylinder(origin=Vertex.Origin(),
583
- radius=radius,
584
- height=cyl_height,
585
- uSides=uSides, vSides=vSidesMiddle, direction=[0, 0, 1], placement="center", tolerance=tolerance)
586
- o1 = Vertex.ByCoordinates(0, 0, cyl_height*0.5)
587
- o2 = Vertex.ByCoordinates(0, 0, -cyl_height*0.5)
588
- s1 = Cell.Sphere(origin=o1, radius=radius, uSides=uSides, vSides=vSidesEnds*2, tolerance=tolerance)
589
- s2 = Cell.Sphere(origin=o2, radius=radius, uSides=uSides, vSides=vSidesEnds*2, tolerance=tolerance)
590
- capsule = Topology.Union(cyl, s1, tolerance=tolerance)
591
- capsule = Topology.Union(capsule, s2, tolerance=tolerance)
592
- if placement == "bottom":
593
- capsule = Topology.Translate(capsule, 0, 0, height/2)
594
- if placement == "lowerleft":
595
- capsule = Topology.Translate(capsule, 0, 0, height/2)
596
- capsule = Topology.Translate(capsule, radius, radius)
597
-
598
- capsule = Topology.Orient(capsule, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
599
- capsule = Topology.Place(capsule, originA=Vertex.Origin(), originB=origin)
600
- return capsule
601
-
602
- @staticmethod
603
- def Compactness(cell: topologic.Cell, reference = "sphere", mantissa: int = 6) -> float:
604
- """
605
- Returns the compactness measure of the input cell. If the reference is "sphere", this is also known as 'sphericity' (https://en.wikipedia.org/wiki/Sphericity).
606
-
607
- Parameters
608
- ----------
609
- cell : topologic.Cell
610
- The input cell.
611
- reference : str , optional
612
- The desired reference to which to compare this compactness. The options are "sphere" and "cube". It is case insensitive. The default is "sphere".
613
- mantissa : int , optional
614
- The desired length of the mantissa. The default is 6.
615
-
616
- Returns
617
- -------
618
- float
619
- The compactness of the input cell.
620
-
621
- """
622
- from topologicpy.Face import Face
623
- faces = []
624
- _ = cell.Faces(None, faces)
625
- area = 0.0
626
- for aFace in faces:
627
- area = area + abs(Face.Area(aFace))
628
- volume = abs(topologic.CellUtility.Volume(cell))
629
- compactness = 0
630
- #From https://en.wikipedia.org/wiki/Sphericity
631
- if area > 0:
632
- if reference.lower() == "sphere":
633
- compactness = (((math.pi)**(1/3))*((6*volume)**(2/3)))/area
634
- else:
635
- compactness = 6*(volume**(2/3))/area
636
- else:
637
- print("Cell.Compactness - Error: cell surface area is not positive. Returning None.")
638
- return None
639
- return round(compactness, mantissa)
640
-
641
- @staticmethod
642
- 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],
643
- dirZ: float = 1, placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
644
- """
645
- Creates a cone.
646
-
647
- Parameters
648
- ----------
649
- origin : topologic.Vertex , optional
650
- The location of the origin of the cone. The default is None which results in the cone being placed at (0, 0, 0).
651
- baseRadius : float , optional
652
- The radius of the base circle of the cone. The default is 0.5.
653
- topRadius : float , optional
654
- The radius of the top circle of the cone. The default is 0.
655
- height : float , optional
656
- The height of the cone. The default is 1.
657
- sides : int , optional
658
- The number of sides of the cone. The default is 16.
659
- direction : list , optional
660
- The vector representing the up direction of the cone. The default is [0, 0, 1].
661
- placement : str , optional
662
- 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".
663
- tolerance : float , optional
664
- The desired tolerance. The default is 0.0001.
665
-
666
- Returns
667
- -------
668
- topologic.Cell
669
- The created cone.
670
-
671
- """
672
- from topologicpy.Vertex import Vertex
673
- from topologicpy.Wire import Wire
674
- from topologicpy.Face import Face
675
- from topologicpy.Shell import Shell
676
- from topologicpy.Cluster import Cluster
677
- from topologicpy.Topology import Topology
678
- def createCone(baseWire, topWire, baseVertex, topVertex, tolerance):
679
- if baseWire == None and topWire == None:
680
- raise Exception("Cell.Cone - Error: Both radii of the cone cannot be zero at the same time")
681
- elif baseWire == None:
682
- apex = baseVertex
683
- wire = topWire
684
- elif topWire == None:
685
- apex = topVertex
686
- wire = baseWire
687
- else:
688
- return topologic.CellUtility.ByLoft([baseWire, topWire])
689
- vertices = []
690
- _ = wire.Vertices(None,vertices)
691
- faces = [Face.ByWire(wire, tolerance=tolerance)]
692
- for i in range(0, len(vertices)-1):
693
- w = Wire.ByVertices([apex, vertices[i], vertices[i+1]])
694
- f = Face.ByWire(w, tolerance=tolerance)
695
- faces.append(f)
696
- w = Wire.ByVertices([apex, vertices[-1], vertices[0]])
697
- f = Face.ByWire(w, tolerance=tolerance)
698
- faces.append(f)
699
- return Cell.ByFaces(faces, tolerance=tolerance)
700
- if not origin:
701
- origin = Vertex.ByCoordinates(0, 0, 0)
702
- if not isinstance(origin, topologic.Vertex):
703
- print("Cell.Cone - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
704
- return None
705
- xOffset = 0
706
- yOffset = 0
707
- zOffset = 0
708
- if placement.lower() == "center":
709
- xOffset = 0
710
- yOffset = 0
711
- zOffset = -height*0.5
712
- elif placement.lower() == "lowerleft":
713
- xOffset = max(baseRadius, topRadius)
714
- yOffset = max(baseRadius, topRadius)
715
- zOffset = 0
716
-
717
- baseZ = origin.Z() + zOffset
718
- topZ = origin.Z() + zOffset + height
719
- baseV = []
720
- topV = []
721
- for i in range(uSides):
722
- angle = math.radians(360/uSides)*i
723
- if baseRadius > 0:
724
- baseX = math.cos(angle)*baseRadius + origin.X() + xOffset
725
- baseY = math.sin(angle)*baseRadius + origin.Y() + yOffset
726
- baseZ = origin.Z() + zOffset
727
- baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ))
728
- if topRadius > 0:
729
- topX = math.cos(angle)*topRadius + origin.X() + xOffset
730
- topY = math.sin(angle)*topRadius + origin.Y() + yOffset
731
- topV.append(Vertex.ByCoordinates(topX,topY,topZ))
732
- if baseRadius > 0:
733
- baseWire = Wire.ByVertices(baseV)
734
- else:
735
- baseWire = None
736
- if topRadius > 0:
737
- topWire = Wire.ByVertices(topV)
738
- else:
739
- topWire = None
740
- baseVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset)
741
- topVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset+height)
742
- cone = createCone(baseWire, topWire, baseVertex, topVertex, tolerance)
743
- if cone == None:
744
- print("Cell.Cone - Error: Could not create a cone. Returning None.")
745
- return None
746
-
747
- if vSides > 1:
748
- cutting_planes = []
749
- baseX = origin.X() + xOffset
750
- baseY = origin.Y() + yOffset
751
- size = max(baseRadius, topRadius)*3
752
- for i in range(1, vSides):
753
- baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i
754
- tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ)
755
- cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size), tolerance=tolerance))
756
- cutting_planes_cluster = Cluster.ByTopologies(cutting_planes)
757
- shell = Cell.Shells(cone)[0]
758
- shell = shell.Slice(cutting_planes_cluster)
759
- cone = Cell.ByShell(shell)
760
- cone = Topology.Orient(cone, origin=origin, dirA=[0, 0, 1], dirB=direction)
761
- return cone
762
-
763
- @staticmethod
764
- def ContainmentStatus(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> int:
765
- """
766
- Returns the containment status of the input vertex in relationship to the input cell
767
-
768
- Parameters
769
- ----------
770
- cell : topologic.Cell
771
- The input cell.
772
- vertex : topologic.Vertex
773
- The input vertex.
774
- tolerance : float , optional
775
- The desired tolerance. The default is 0.0001.
776
-
777
- Returns
778
- -------
779
- int
780
- Returns 0 if the vertex is inside, 1 if it is on the boundary of, and 2 if it is outside the input cell.
781
-
782
- """
783
- if not isinstance(cell, topologic.Cell):
784
- print("Cell.ContainmentStatus - Error: The input cell parameter is not a valid topologic cell. Returning None.")
785
- return None
786
- if not isinstance(vertex, topologic.Vertex):
787
- print("Cell.ContainmentStatus - Error: The input vertex parameter is not a valid topologic vertex. Returning None.")
788
- return None
789
- try:
790
- status = topologic.CellUtility.Contains(cell, vertex, tolerance)
791
- if status == 0:
792
- return 0
793
- elif status == 1:
794
- return 1
795
- else:
796
- return 2
797
- except:
798
- print("Cell.ContainmentStatus - Error: Could not determine containment status. Returning None.")
799
- return None
800
-
801
- @staticmethod
802
- def Cylinder(origin: topologic.Vertex = None, radius: float = 0.5, height: float = 1, uSides: int = 16, vSides: int = 1, direction: list = [0, 0, 1],
803
- placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
804
- """
805
- Creates a cylinder.
806
-
807
- Parameters
808
- ----------
809
- origin : topologic.Vertex , optional
810
- The location of the origin of the cylinder. The default is None which results in the cylinder being placed at (0, 0, 0).
811
- radius : float , optional
812
- The radius of the cylinder. The default is 0.5.
813
- height : float , optional
814
- The height of the cylinder. The default is 1.
815
- uSides : int , optional
816
- The number of circle segments of the cylinder. The default is 16.
817
- vSides : int , optional
818
- The number of vertical segments of the cylinder. The default is 1.
819
- direction : list , optional
820
- The vector representing the up direction of the cylinder. The default is [0, 0, 1].
821
- placement : str , optional
822
- 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".
823
- tolerance : float , optional
824
- The desired tolerance. The default is 0.0001.
825
-
826
- Returns
827
- -------
828
- topologic.Cell
829
- The created cell.
830
-
831
- """
832
- from topologicpy.Vertex import Vertex
833
- from topologicpy.Face import Face
834
- from topologicpy.CellComplex import CellComplex
835
- from topologicpy.Cluster import Cluster
836
- from topologicpy.Topology import Topology
837
- if not origin:
838
- origin = Vertex.ByCoordinates(0, 0, 0)
839
- if not isinstance(origin, topologic.Vertex):
840
- print("Cell.Cylinder - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
841
- return None
842
- xOffset = 0
843
- yOffset = 0
844
- zOffset = 0
845
- if placement.lower() == "center":
846
- zOffset = -height*0.5
847
- elif placement.lower() == "lowerleft":
848
- xOffset = radius
849
- yOffset = radius
850
- circle_origin = Vertex.ByCoordinates(origin.X() + xOffset, origin.Y() + yOffset, origin.Z() + zOffset)
851
-
852
- baseWire = Wire.Circle(origin=circle_origin, radius=radius, sides=uSides, fromAngle=0, toAngle=360, close=True, direction=[0, 0, 1], placement="center", tolerance=tolerance)
853
- baseFace = Face.ByWire(baseWire, tolerance=tolerance)
854
- cylinder = Cell.ByThickenedFace(face=baseFace, thickness=height, bothSides=False, reverse=True,
855
- tolerance=tolerance)
856
- if vSides > 1:
857
- cutting_planes = []
858
- baseX = origin.X() + xOffset
859
- baseY = origin.Y() + yOffset
860
- size = radius*3
861
- for i in range(1, vSides):
862
- baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i
863
- tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ)
864
- cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size), tolerance=tolerance))
865
- cutting_planes_cluster = Cluster.ByTopologies(cutting_planes)
866
- cylinder = CellComplex.ExternalBoundary(cylinder.Slice(cutting_planes_cluster))
867
-
868
- cylinder = Topology.Orient(cylinder, origin=origin, dirA=[0, 0, 1], dirB=direction)
869
- return cylinder
870
-
871
- @staticmethod
872
- def Decompose(cell: topologic.Cell, tiltAngle: float = 10, tolerance: float = 0.0001) -> dict:
873
- """
874
- Decomposes the input cell into its logical components. This method assumes that the positive Z direction is UP.
875
-
876
- Parameters
877
- ----------
878
- cell : topologic.Cell
879
- the input cell.
880
- tiltAngle : float , optional
881
- 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.
882
- tolerance : float , optional
883
- The desired tolerance. The default is 0.0001.
884
-
885
- Returns
886
- -------
887
- dictionary
888
- A dictionary with the following keys and values:
889
- 1. "verticalFaces": list of vertical faces
890
- 2. "topHorizontalFaces": list of top horizontal faces
891
- 3. "bottomHorizontalFaces": list of bottom horizontal faces
892
- 4. "inclinedFaces": list of inclined faces
893
- 5. "verticalApertures": list of vertical apertures
894
- 6. "topHorizontalApertures": list of top horizontal apertures
895
- 7. "bottomHorizontalApertures": list of bottom horizontal apertures
896
- 8. "inclinedApertures": list of inclined apertures
897
-
898
- """
899
- from topologicpy.Face import Face
900
- from topologicpy.Vector import Vector
901
- from topologicpy.Aperture import Aperture
902
- from topologicpy.Topology import Topology
903
-
904
- def angleCode(f, up, tiltAngle):
905
- dirA = Face.NormalAtParameters(f)
906
- ang = round(Vector.Angle(dirA, up), 2)
907
- if abs(ang - 90) < tiltAngle:
908
- code = 0
909
- elif abs(ang) < tiltAngle:
910
- code = 1
911
- elif abs(ang - 180) < tiltAngle:
912
- code = 2
913
- else:
914
- code = 3
915
- return code
916
-
917
- def getApertures(topology):
918
- apTopologies = []
919
- apertures = Topology.Apertures(topology)
920
- if isinstance(apertures, list):
921
- for aperture in apertures:
922
- apTopologies.append(Aperture.Topology(aperture))
923
- return apTopologies
924
-
925
- if not isinstance(cell, topologic.Cell):
926
- print("Cell.Decompose - Error: The input cell parameter is not a valid topologic cell. Returning None.")
927
- return None
928
- verticalFaces = []
929
- topHorizontalFaces = []
930
- bottomHorizontalFaces = []
931
- inclinedFaces = []
932
- verticalApertures = []
933
- topHorizontalApertures = []
934
- bottomHorizontalApertures = []
935
- inclinedApertures = []
936
- tiltAngle = abs(tiltAngle)
937
- faces = Cell.Faces(cell)
938
- zList = []
939
- for f in faces:
940
- zList.append(f.Centroid().Z())
941
- zMin = min(zList)
942
- zMax = max(zList)
943
- up = [0, 0, 1]
944
- for aFace in faces:
945
- aCode = angleCode(aFace, up, tiltAngle)
946
-
947
- if aCode == 0:
948
- verticalFaces.append(aFace)
949
- verticalApertures += getApertures(aFace)
950
- elif aCode == 1:
951
- if abs(aFace.Centroid().Z() - zMin) < tolerance:
952
- bottomHorizontalFaces.append(aFace)
953
- bottomHorizontalApertures += getApertures(aFace)
954
- else:
955
- topHorizontalFaces.append(aFace)
956
- topHorizontalApertures += getApertures(aFace)
957
- elif aCode == 2:
958
- if abs(aFace.Centroid().Z() - zMax) < tolerance:
959
- topHorizontalFaces.append(aFace)
960
- topHorizontalApertures += getApertures(aFace)
961
- else:
962
- bottomHorizontalFaces.append(aFace)
963
- bottomHorizontalApertures += getApertures(aFace)
964
- elif aCode == 3:
965
- inclinedFaces.append(aFace)
966
- inclinedApertures += getApertures(aFace)
967
- d = {
968
- "verticalFaces" : verticalFaces,
969
- "topHorizontalFaces" : topHorizontalFaces,
970
- "bottomHorizontalFaces" : bottomHorizontalFaces,
971
- "inclinedFaces" : inclinedFaces,
972
- "verticalApertures" : verticalApertures,
973
- "topHorizontalApertures" : topHorizontalApertures,
974
- "bottomHorizontalApertures" : bottomHorizontalApertures,
975
- "inclinedApertures" : inclinedApertures
976
- }
977
- return d
978
-
979
- @staticmethod
980
- def Dodecahedron(origin: topologic.Vertex = None, radius: float = 0.5,
981
- direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
982
- """
983
- Description
984
- ----------
985
- Creates a dodecahedron. See https://en.wikipedia.org/wiki/Dodecahedron.
986
-
987
- Parameters
988
- ----------
989
- origin : topologic.Vertex , optional
990
- The origin location of the dodecahedron. The default is None which results in the dodecahedron being placed at (0, 0, 0).
991
- radius : float , optional
992
- The radius of the dodecahedron's circumscribed sphere. The default is 0.5.
993
- direction : list , optional
994
- The vector representing the up direction of the dodecahedron. The default is [0, 0, 1].
995
- placement : str , optional
996
- The description of the placement of the origin of the dodecahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
997
- tolerance : float , optional
998
- The desired tolerance. The default is 0.0001.
999
-
1000
- Returns
1001
- -------
1002
- topologic.Cell
1003
- The created dodecahedron.
1004
-
1005
- """
1006
- from topologicpy.Vertex import Vertex
1007
- from topologicpy.Edge import Edge
1008
- from topologicpy.Face import Face
1009
- from topologicpy.Cluster import Cluster
1010
- from topologicpy.Topology import Topology
1011
-
1012
- if not origin:
1013
- origin = Vertex.ByCoordinates(0, 0, 0)
1014
- if not isinstance(origin, topologic.Vertex):
1015
- print("Cell.Dodecahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1016
- return None
1017
- pen = Face.Circle(sides=5, radius=0.5)
1018
- pentagons = [pen]
1019
- edges = Topology.Edges(pen)
1020
- for edge in edges:
1021
- o = Topology.Centroid(edge)
1022
- e_dir = Edge.Direction(edge)
1023
- pentagons.append(Topology.Rotate(pen, origin=o, axis=e_dir, angle=116.565))
1024
-
1025
- cluster = Cluster.ByTopologies(pentagons)
1026
-
1027
- cluster2 = Topology.Rotate(cluster, origin=Vertex.Origin(), axis=[1, 0, 0], angle=180)
1028
- cluster2 = Topology.Rotate(cluster2, origin=Vertex.Origin(), axis=[0, 0, 1], angle=36)
1029
- vertices = Topology.Vertices(cluster2)
1030
- zList = [Vertex.Z(v) for v in vertices]
1031
- zList = list(set(zList))
1032
- zList.sort()
1033
- zoffset1 = zList[-1] - zList[0]
1034
- zoffset2 = zList[1] - zList[0]
1035
- cluster2 = Topology.Translate(cluster2, 0, 0, -zoffset1-zoffset2)
1036
- pentagons += Topology.Faces(cluster2)
1037
- dodecahedron = Cell.ByFaces(pentagons, tolerance=tolerance)
1038
- centroid = Topology.Centroid(dodecahedron)
1039
- dodecahedron = Topology.Translate(dodecahedron, -Vertex.X(centroid), -Vertex.Y(centroid), -Vertex.Z(centroid))
1040
- vertices = Topology.Vertices(dodecahedron)
1041
- d = Vertex.Distance(Vertex.Origin(), vertices[0])
1042
- dodecahedron = Topology.Scale(dodecahedron, origin=Vertex.Origin(), x=radius/d, y=radius/d, z=radius/d)
1043
- if placement == "bottom":
1044
- dodecahedron = Topology.Translate(dodecahedron, 0, 0, radius)
1045
- elif placement == "lowerleft":
1046
- dodecahedron = Topology.Translate(dodecahedron, radius, radius, radius)
1047
-
1048
- dodecahedron = Topology.Orient(dodecahedron, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1049
- dodecahedron = Topology.Place(dodecahedron, originA=Vertex.Origin(), originB=origin)
1050
- return dodecahedron
1051
-
1052
- @staticmethod
1053
- def Edges(cell: topologic.Cell) -> list:
1054
- """
1055
- Returns the edges of the input cell.
1056
-
1057
- Parameters
1058
- ----------
1059
- cell : topologic.Cell
1060
- The input cell.
1061
-
1062
- Returns
1063
- -------
1064
- list
1065
- The list of edges.
1066
-
1067
- """
1068
- if not isinstance(cell, topologic.Cell):
1069
- print("Cell.Edges - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1070
- return None
1071
- edges = []
1072
- _ = cell.Edges(None, edges)
1073
- return edges
1074
-
1075
- @staticmethod
1076
- def Egg(origin: topologic.Vertex = None, height: float = 1.0, uSides: int = 16, vSides: int = 8, direction: list = [0, 0, 1],
1077
- placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1078
- """
1079
- Creates a sphere.
1080
-
1081
- Parameters
1082
- ----------
1083
- origin : topologic.Vertex , optional
1084
- The origin location of the sphere. The default is None which results in the sphere being placed at (0, 0, 0).
1085
- radius : float , optional
1086
- The radius of the sphere. The default is 0.5.
1087
- uSides : int , optional
1088
- The number of sides along the longitude of the sphere. The default is 16.
1089
- vSides : int , optional
1090
- The number of sides along the latitude of the sphere. The default is 8.
1091
- direction : list , optional
1092
- The vector representing the up direction of the sphere. The default is [0, 0, 1].
1093
- placement : str , optional
1094
- 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".
1095
- tolerance : float , optional
1096
- The desired tolerance. The default is 0.0001.
1097
-
1098
- Returns
1099
- -------
1100
- topologic.Cell
1101
- The created sphere.
1102
-
1103
- """
1104
-
1105
- from topologicpy.Vertex import Vertex
1106
- from topologicpy.Topology import Topology
1107
- from topologicpy.Dictionary import Dictionary
1108
-
1109
- if not origin:
1110
- origin = Vertex.ByCoordinates(0, 0, 0)
1111
- if not isinstance(origin, topologic.Vertex):
1112
- print("Cell.Sphere - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1113
- return None
1114
-
1115
- coords = [[0.0, 0.0, -0.5],
1116
- [0.074748, 0.0, -0.494015],
1117
- [0.140819, 0.0, -0.473222],
1118
- [0.204118, 0.0, -0.438358],
1119
- [0.259512, 0.0, -0.391913],
1120
- [0.304837, 0.0, -0.335519],
1121
- [0.338649, 0.0, -0.271416],
1122
- [0.361307, 0.0, -0.202039],
1123
- [0.375678, 0.0, -0.129109],
1124
- [0.381294, 0.0, -0.053696],
1125
- [0.377694, 0.0, 0.019874],
1126
- [0.365135, 0.0, 0.091978],
1127
- [0.341482, 0.0, 0.173973],
1128
- [0.300154, 0.0, 0.276001],
1129
- [0.252928, 0.0, 0.355989],
1130
- [0.206605, 0.0, 0.405813],
1131
- [0.157529, 0.0, 0.442299],
1132
- [0.10604, 0.0, 0.472092],
1133
- [0.05547, 0.0, 0.491784],
1134
- [0.0, 0.0, 0.5]]
1135
- verts = [Vertex.ByCoordinates(coord) for coord in coords]
1136
- c = Wire.ByVertices(verts, close=False)
1137
- new_verts = []
1138
- for i in range(vSides+1):
1139
- new_verts.append(Wire.VertexByParameter(c, i/vSides))
1140
- c = Wire.ByVertices(new_verts, close=False)
1141
- egg = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
1142
- if egg.Type() == topologic.CellComplex.Type():
1143
- egg = egg.ExternalBoundary()
1144
- if egg.Type() == topologic.Shell.Type():
1145
- egg = topologic.Cell.ByShell(egg)
1146
- egg = Topology.Scale(egg, origin=Vertex.Origin(), x=height, y=height, z=height)
1147
- if placement.lower() == "bottom":
1148
- egg = Topology.Translate(egg, 0, 0, height/2)
1149
- elif placement.lower() == "lowerleft":
1150
- bb = Cell.BoundingBox(egg)
1151
- d = Topology.Dictionary(bb)
1152
- width = Dictionary.ValueAtKey(d, 'width')
1153
- length = Dictionary.ValueAtKey(d, 'length')
1154
- egg = Topology.Translate(egg, width*0.5, length*0.5, height*0.5)
1155
- egg = Topology.Orient(egg, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1156
- egg = Topology.Place(egg, originA=Vertex.Origin(), originB=origin)
1157
- return egg
1158
-
1159
- @staticmethod
1160
- def ExternalBoundary(cell: topologic.Cell) -> topologic.Shell:
1161
- """
1162
- Returns the external boundary of the input cell.
1163
-
1164
- Parameters
1165
- ----------
1166
- cell : topologic.Cell
1167
- The input cell.
1168
-
1169
- Returns
1170
- -------
1171
- topologic.Shell
1172
- The external boundary of the input cell.
1173
-
1174
- """
1175
-
1176
- if not isinstance(cell, topologic.Cell):
1177
- print("Cell.ExternalBoundary - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1178
- return None
1179
- try:
1180
- return cell.ExternalBoundary()
1181
- except:
1182
- print("Cell.ExternalBoundary - Error: Could not compute the external boundary. Returning None.")
1183
- return None
1184
-
1185
- @staticmethod
1186
- def Faces(cell: topologic.Cell) -> list:
1187
- """
1188
- Returns the faces of the input cell.
1189
-
1190
- Parameters
1191
- ----------
1192
- cell : topologic.Cell
1193
- The input cell.
1194
-
1195
- Returns
1196
- -------
1197
- list
1198
- The list of faces.
1199
-
1200
- """
1201
- if not isinstance(cell, topologic.Cell):
1202
- print("Cell.Faces - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1203
- return None
1204
- faces = []
1205
- _ = cell.Faces(None, faces)
1206
- return faces
1207
-
1208
- @staticmethod
1209
- def Hyperboloid(origin: topologic.Cell = None, baseRadius: float = 0.5, topRadius: float = 0.5, height: float = 1, sides: int = 24, direction: list = [0, 0, 1],
1210
- twist: float = 60, placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1211
- """
1212
- Creates a hyperboloid.
1213
-
1214
- Parameters
1215
- ----------
1216
- origin : topologic.Vertex , optional
1217
- The location of the origin of the hyperboloid. The default is None which results in the hyperboloid being placed at (0, 0, 0).
1218
- baseRadius : float , optional
1219
- The radius of the base circle of the hyperboloid. The default is 0.5.
1220
- topRadius : float , optional
1221
- The radius of the top circle of the hyperboloid. The default is 0.5.
1222
- height : float , optional
1223
- The height of the cone. The default is 1.
1224
- sides : int , optional
1225
- The number of sides of the cone. The default is 24.
1226
- direction : list , optional
1227
- The vector representing the up direction of the hyperboloid. The default is [0, 0, 1].
1228
- twist : float , optional
1229
- The angle to twist the base cylinder. The default is 60.
1230
- placement : str , optional
1231
- 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".
1232
- tolerance : float , optional
1233
- The desired tolerance. The default is 0.0001.
1234
-
1235
- Returns
1236
- -------
1237
- topologic.Cell
1238
- The created hyperboloid.
1239
-
1240
- """
1241
- from topologicpy.Cluster import Cluster
1242
- from topologicpy.Vertex import Vertex
1243
- from topologicpy.Face import Face
1244
- from topologicpy.Topology import Topology
1245
-
1246
- def createHyperboloid(baseVertices, topVertices, tolerance):
1247
- baseWire = Wire.ByVertices(baseVertices, close=True)
1248
- topWire = Wire.ByVertices(topVertices, close=True)
1249
- baseFace = Face.ByWire(baseWire, tolerance=tolerance)
1250
- topFace = Face.ByWire(topWire, tolerance=tolerance)
1251
- faces = [baseFace, topFace]
1252
- for i in range(0, len(baseVertices)-1):
1253
- w = Wire.ByVertices([baseVertices[i], topVertices[i], topVertices[i+1]], close=True)
1254
- f = Face.ByWire(w, tolerance=tolerance)
1255
- faces.append(f)
1256
- w = Wire.ByVertices([baseVertices[i+1], baseVertices[i], topVertices[i+1]], close=True)
1257
- f = Face.ByWire(w, tolerance=tolerance)
1258
- faces.append(f)
1259
- w = Wire.ByVertices([baseVertices[-1], topVertices[-1], topVertices[0]], close=True)
1260
- f = Face.ByWire(w, tolerance=tolerance)
1261
- faces.append(f)
1262
- w = Wire.ByVertices([baseVertices[0], baseVertices[-1], topVertices[0]], close=True)
1263
- f = Face.ByWire(w, tolerance=tolerance)
1264
- faces.append(f)
1265
- returnTopology = Cell.ByFaces(faces, tolerance=tolerance)
1266
- if returnTopology == None:
1267
- returnTopology = Cluster.ByTopologies(faces)
1268
- return returnTopology
1269
-
1270
- if not origin:
1271
- origin = Vertex.ByCoordinates(0, 0, 0)
1272
- if not isinstance(origin, topologic.Vertex):
1273
- print("Cell.Hyperboloid - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1274
- return None
1275
- w_origin = Vertex.Origin()
1276
- baseV = []
1277
- topV = []
1278
- xOffset = 0
1279
- yOffset = 0
1280
- zOffset = 0
1281
- if placement.lower() == "center":
1282
- zOffset = -height*0.5
1283
- elif placement.lower() == "lowerleft":
1284
- xOffset = max(baseRadius, topRadius)
1285
- yOffset = max(baseRadius, topRadius)
1286
- baseZ = w_origin.Z() + zOffset
1287
- topZ = w_origin.Z() + zOffset + height
1288
- for i in range(sides):
1289
- angle = math.radians(360/sides)*i
1290
- if baseRadius > 0:
1291
- baseX = math.sin(angle+math.radians(twist))*baseRadius + w_origin.X() + xOffset
1292
- baseY = math.cos(angle+math.radians(twist))*baseRadius + w_origin.Y() + yOffset
1293
- baseZ = w_origin.Z() + zOffset
1294
- baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ))
1295
- if topRadius > 0:
1296
- topX = math.sin(angle-math.radians(twist))*topRadius + w_origin.X() + xOffset
1297
- topY = math.cos(angle-math.radians(twist))*topRadius + w_origin.Y() + yOffset
1298
- topV.append(Vertex.ByCoordinates(topX,topY,topZ))
1299
-
1300
- hyperboloid = createHyperboloid(baseV, topV, tolerance)
1301
- if hyperboloid == None:
1302
- print("Cell.Hyperboloid - Error: Could not create a hyperboloid. Returning None.")
1303
- return None
1304
-
1305
- hyperboloid = Topology.Orient(hyperboloid, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1306
- hyperboloid = Topology.Place(hyperboloid, originA=Vertex.Origin(), originB=origin)
1307
- return hyperboloid
1308
-
1309
- @staticmethod
1310
- def Icosahedron(origin: topologic.Vertex = None, radius: float = 0.5,
1311
- direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1312
- """
1313
- Description
1314
- ----------
1315
- Creates an icosahedron. See https://en.wikipedia.org/wiki/Icosahedron.
1316
-
1317
- Parameters
1318
- ----------
1319
- origin : topologic.Vertex , optional
1320
- The origin location of the icosahedron. The default is None which results in the icosahedron being placed at (0, 0, 0).
1321
- radius : float , optional
1322
- The radius of the icosahedron's circumscribed sphere. The default is 0.5.
1323
- direction : list , optional
1324
- The vector representing the up direction of the icosahedron. The default is [0, 0, 1].
1325
- placement : str , optional
1326
- The description of the placement of the origin of the icosahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1327
- tolerance : float , optional
1328
- The desired tolerance. The default is 0.0001.
1329
-
1330
- Returns
1331
- -------
1332
- topologic.Cell
1333
- The created icosahedron.
1334
-
1335
- """
1336
- from topologicpy.Vertex import Vertex
1337
- from topologicpy.Wire import Wire
1338
- from topologicpy.Face import Face
1339
- from topologicpy.Topology import Topology
1340
- import math
1341
-
1342
- if not origin:
1343
- origin = Vertex.ByCoordinates(0, 0, 0)
1344
- if not isinstance(origin, topologic.Vertex):
1345
- print("Cell.Dodecahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1346
- return None
1347
- rect1 = Wire.Rectangle(width=(1+math.sqrt(5))/2, length=1)
1348
- rect2 = Wire.Rectangle(width=1, length=(1+math.sqrt(5))/2)
1349
- rect2 = Topology.Rotate(rect2, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
1350
- rect3 = Wire.Rectangle(width=1, length=(1+math.sqrt(5))/2)
1351
- rect3 = Topology.Rotate(rect3, origin=Vertex.Origin(), axis=[0, 1, 0], angle=90)
1352
- vertices = Topology.Vertices(rect1)
1353
- v1, v2, v3, v4 = vertices
1354
- vertices = Topology.Vertices(rect2)
1355
- v5, v6, v7, v8 = vertices
1356
- vertices = Topology.Vertices(rect3)
1357
- v9, v10, v11, v12 = vertices
1358
- f1 = Face.ByVertices([v1,v8,v4])
1359
- f2 = Face.ByVertices([v1,v4,v5])
1360
- f3 = Face.ByVertices([v3,v2,v6])
1361
- f4 = Face.ByVertices([v2,v3,v7])
1362
- f5 = Face.ByVertices([v10,v9,v2])
1363
- f6 = Face.ByVertices([v10,v9,v1])
1364
- f7 = Face.ByVertices([v12,v11,v4])
1365
- f8 = Face.ByVertices([v12,v11,v3])
1366
- f9 = Face.ByVertices([v8,v7,v9])
1367
- f10 = Face.ByVertices([v8,v7,v12])
1368
- f11 = Face.ByVertices([v5,v6,v10])
1369
- f12 = Face.ByVertices([v5,v6,v11])
1370
- f13 = Face.ByVertices([v8,v1,v9])
1371
- f14 = Face.ByVertices([v9,v2,v7])
1372
- f15 = Face.ByVertices([v7,v3,v12])
1373
- f16 = Face.ByVertices([v8,v12,v4])
1374
- f17 = Face.ByVertices([v1,v5,v10])
1375
- f18 = Face.ByVertices([v10,v2,v6])
1376
- f19 = Face.ByVertices([v6,v3,v11])
1377
- f20 = Face.ByVertices([v11,v4,v5])
1378
-
1379
- icosahedron = Cell.ByFaces([f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,
1380
- f11,f12,f13,f14,f15,f16,f17,f18,f19,f20], tolerance=tolerance)
1381
- sf = 1.051*0.5 # To insribe it in a sphere of radius 0.5
1382
- icosahedron = Topology.Scale(icosahedron, origin=Vertex.Origin(), x=sf, y=sf, z=sf)
1383
- sf = radius/0.5
1384
- icosahedron = Topology.Scale(icosahedron, origin=Vertex.Origin(), x=sf, y=sf, z=sf)
1385
- if placement == "bottom":
1386
- icosahedron = Topology.Translate(icosahedron, 0, 0, radius)
1387
- elif placement == "lowerleft":
1388
- icosahedron = Topology.Translate(icosahedron, radius, radius, radius)
1389
-
1390
- icosahedron = Topology.Orient(icosahedron, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1391
- icosahedron = Topology.Place(icosahedron, originA=Vertex.Origin(), originB=origin)
1392
- return icosahedron
1393
-
1394
-
1395
- @staticmethod
1396
- def InternalBoundaries(cell: topologic.Cell) -> list:
1397
- """
1398
- Returns the internal boundaries of the input cell.
1399
-
1400
- Parameters
1401
- ----------
1402
- cell : topologic.Cell
1403
- The input cell.
1404
-
1405
- Returns
1406
- -------
1407
- list
1408
- The list of internal boundaries ([topologic.Shell]).
1409
-
1410
- """
1411
- shells = []
1412
- _ = cell.InternalBoundaries(shells)
1413
- return shells
1414
-
1415
- @staticmethod
1416
- def InternalVertex(cell: topologic.Cell, tolerance: float = 0.0001):
1417
- """
1418
- Creates a vertex that is guaranteed to be inside the input cell.
1419
-
1420
- Parameters
1421
- ----------
1422
- cell : topologic.Cell
1423
- The input cell.
1424
- tolerance : float , optional
1425
- The desired tolerance. The default is 0.0001.
1426
-
1427
- Returns
1428
- -------
1429
- topologic.Vertex
1430
- The internal vertex.
1431
-
1432
- """
1433
-
1434
- if not isinstance(cell, topologic.Cell):
1435
- print("Cell.InternalVertex - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1436
- return None
1437
- try:
1438
- return topologic.CellUtility.InternalVertex(cell, tolerance)
1439
- except:
1440
- print("Cell.InternalVertex - Error: Could not create an internal vertex. Returning None.")
1441
- return None
1442
-
1443
- @staticmethod
1444
- def IsOnBoundary(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
1445
- """
1446
- Returns True if the input vertex is on the boundary of the input cell. Returns False otherwise.
1447
-
1448
- Parameters
1449
- ----------
1450
- cell : topologic.Cell
1451
- The input cell.
1452
- vertex : topologic.Vertex
1453
- The input vertex.
1454
- tolerance : float , optional
1455
- The desired tolerance. The default is 0.0001.
1456
-
1457
- Returns
1458
- -------
1459
- bool
1460
- Returns True if the input vertex is inside the input cell. Returns False otherwise.
1461
-
1462
- """
1463
-
1464
- if not isinstance(cell, topologic.Cell):
1465
- print("Cell.IsOnBoundary - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1466
- return None
1467
- if not isinstance(vertex, topologic.Vertex):
1468
- print("Cell.IsOnBoundary - Error: The input vertex parameter is not a valid topologic vertex. Returning None.")
1469
- return None
1470
- try:
1471
- return (topologic.CellUtility.Contains(cell, vertex, tolerance) == 1)
1472
- except:
1473
- print("Cell.IsOnBoundary - Error: Could not determine if the input vertex is on the boundary of the input cell. Returning None.")
1474
- return None
1475
-
1476
- @staticmethod
1477
- def Octahedron(origin: topologic.Vertex = None, radius: float = 0.5,
1478
- direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1479
- """
1480
- Description
1481
- ----------
1482
- Creates an octahedron. See https://en.wikipedia.org/wiki/Octahedron.
1483
-
1484
- Parameters
1485
- ----------
1486
- origin : topologic.Vertex , optional
1487
- The origin location of the octahedron. The default is None which results in the octahedron being placed at (0, 0, 0).
1488
- radius : float , optional
1489
- The radius of the octahedron's circumscribed sphere. The default is 0.5.
1490
- direction : list , optional
1491
- The vector representing the up direction of the octahedron. The default is [0, 0, 1].
1492
- placement : str , optional
1493
- The description of the placement of the origin of the octahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1494
- tolerance : float , optional
1495
- The desired tolerance. The default is 0.0001.
1496
-
1497
- Returns
1498
- -------
1499
- topologic.Cell
1500
- The created octahedron.
1501
-
1502
- """
1503
-
1504
- from topologicpy.Vertex import Vertex
1505
- from topologicpy.Face import Face
1506
- from topologicpy.Topology import Topology
1507
-
1508
- if not origin:
1509
- origin = Vertex.ByCoordinates(0, 0, 0)
1510
- if not isinstance(origin, topologic.Vertex):
1511
- print("Cell.Octahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1512
- return None
1513
-
1514
- vb1 = Vertex.ByCoordinates(-0.5, 0, 0)
1515
- vb2 = Vertex.ByCoordinates(0, -0.5, 0)
1516
- vb3 = Vertex.ByCoordinates(0.5, 0, 0)
1517
- vb4 = Vertex.ByCoordinates(0, 0.5, 0)
1518
- top = Vertex.ByCoordinates(0, 0, 0.5)
1519
- bottom = Vertex.ByCoordinates(0, 0, -0.5)
1520
- f1 = Face.ByVertices([top, vb1, vb2])
1521
- f2 = Face.ByVertices([top, vb2, vb3])
1522
- f3 = Face.ByVertices([top, vb3, vb4])
1523
- f4 = Face.ByVertices([top, vb4, vb1])
1524
- f5 = Face.ByVertices([bottom, vb1, vb2])
1525
- f6 = Face.ByVertices([bottom, vb2, vb3])
1526
- f7 = Face.ByVertices([bottom, vb3, vb4])
1527
- f8 = Face.ByVertices([bottom, vb4, vb1])
1528
-
1529
- octahedron = Cell.ByFaces([f1, f2, f3, f4, f5, f6, f7, f8], tolerance=tolerance)
1530
- octahedron = Topology.Scale(octahedron, origin=Vertex.Origin(), x=radius/0.5, y=radius/0.5, z=radius/0.5)
1531
- if placement == "bottom":
1532
- octahedron = Topology.Translate(octahedron, 0, 0, radius)
1533
- elif placement == "lowerleft":
1534
- octahedron = Topology.Translate(octahedron, radius, radius, radius)
1535
- octahedron = Topology.Orient(octahedron, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1536
- octahedron = Topology.Place(octahedron, originA=Vertex.Origin(), originB=origin)
1537
- return octahedron
1538
-
1539
- @staticmethod
1540
- 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:
1541
- """
1542
- Description
1543
- ----------
1544
- Creates a pipe along the input edge.
1545
-
1546
- Parameters
1547
- ----------
1548
- edge : topologic.Edge
1549
- The centerline of the pipe.
1550
- profile : topologic.Wire , optional
1551
- 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.
1552
- radius : float , optional
1553
- The radius of the pipe. The default is 0.5.
1554
- sides : int , optional
1555
- The number of sides of the pipe. The default is 16.
1556
- startOffset : float , optional
1557
- The offset distance from the start vertex of the centerline edge. The default is 0.
1558
- endOffset : float , optional
1559
- The offset distance from the end vertex of the centerline edge. The default is 0.
1560
- endcapA : topologic.Topology, optional
1561
- 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.
1562
- endcapB : topologic.Topology, optional
1563
- 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.
1564
-
1565
- Returns
1566
- -------
1567
- dict
1568
- A dictionary containing the pipe, the start endcap, and the end endcap if they have been specified. The dictionary has the following keys:
1569
- 'pipe'
1570
- 'endcapA'
1571
- 'endcapB'
1572
-
1573
- """
1574
-
1575
- from topologicpy.Vertex import Vertex
1576
- from topologicpy.Edge import Edge
1577
- from topologicpy.Topology import Topology
1578
-
1579
- if not isinstance(edge, topologic.Edge):
1580
- print("Cell.Pipe - Error: The input edge parameter is not a valid topologic edge. Returning None.")
1581
- return None
1582
- length = Edge.Length(edge)
1583
- origin = Edge.StartVertex(edge)
1584
- startU = startOffset / length
1585
- endU = 1.0 - (endOffset / length)
1586
- sv = Edge.VertexByParameter(edge, startU)
1587
- ev = Edge.VertexByParameter(edge, endU)
1588
- x1 = sv.X()
1589
- y1 = sv.Y()
1590
- z1 = sv.Z()
1591
- x2 = ev.X()
1592
- y2 = ev.Y()
1593
- z2 = ev.Z()
1594
- dx = x2 - x1
1595
- dy = y2 - y1
1596
- dz = z2 - z1
1597
- dist = math.sqrt(dx**2 + dy**2 + dz**2)
1598
- baseV = []
1599
- topV = []
1600
-
1601
- if isinstance(profile, topologic.Wire):
1602
- baseWire = Topology.Translate(profile, 0 , 0, sv.Z())
1603
- topWire = Topology.Translate(profile, 0 , 0, sv.Z()+dist)
1604
- else:
1605
- for i in range(sides):
1606
- angle = math.radians(360/sides)*i
1607
- x = math.sin(angle)*radius + sv.X()
1608
- y = math.cos(angle)*radius + sv.Y()
1609
- z = sv.Z()
1610
- baseV.append(Vertex.ByCoordinates(x, y, z))
1611
- topV.append(Vertex.ByCoordinates(x, y, z+dist))
1612
-
1613
- baseWire = Wire.ByVertices(baseV)
1614
- topWire = Wire.ByVertices(topV)
1615
- wires = [baseWire, topWire]
1616
- pipe = Cell.ByWires(wires)
1617
- phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1618
- if dist < 0.0001:
1619
- theta = 0
1620
- else:
1621
- theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1622
- pipe = Topology.Rotate(pipe, origin=sv, axis=[0, 1, 0], angle=theta)
1623
- pipe = Topology.Rotate(pipe, origin=sv, axis=[0, 0, 1], angle=phi)
1624
- zzz = Vertex.ByCoordinates(0, 0, 0)
1625
- if endcapA:
1626
- origin = edge.StartVertex()
1627
- x1 = origin.X()
1628
- y1 = origin.Y()
1629
- z1 = origin.Z()
1630
- x2 = edge.EndVertex().X()
1631
- y2 = edge.EndVertex().Y()
1632
- z2 = edge.EndVertex().Z()
1633
- dx = x2 - x1
1634
- dy = y2 - y1
1635
- dz = z2 - z1
1636
- dist = math.sqrt(dx**2 + dy**2 + dz**2)
1637
- phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1638
- if dist < 0.0001:
1639
- theta = 0
1640
- else:
1641
- theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1642
- endcapA = Topology.Copy(endcapA)
1643
- endcapA = Topology.Rotate(endcapA, origin=zzz, axis=[0, 1, 0], angle=theta)
1644
- endcapA = Topology.Rotate(endcapA, origin=zzz, axis=[0, 0, 1], angle=phi+180)
1645
- endcapA = Topology.Translate(endcapA, origin.X(), origin.Y(), origin.Z())
1646
- if endcapB:
1647
- origin = edge.EndVertex()
1648
- x1 = origin.X()
1649
- y1 = origin.Y()
1650
- z1 = origin.Z()
1651
- x2 = edge.StartVertex().X()
1652
- y2 = edge.StartVertex().Y()
1653
- z2 = edge.StartVertex().Z()
1654
- dx = x2 - x1
1655
- dy = y2 - y1
1656
- dz = z2 - z1
1657
- dist = math.sqrt(dx**2 + dy**2 + dz**2)
1658
- phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1659
- if dist < 0.0001:
1660
- theta = 0
1661
- else:
1662
- theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1663
- endcapB = Topology.Copy(endcapB)
1664
- endcapB = Topology.Rotate(endcapB, origin=zzz, axis=[0, 1, 0], angle=theta)
1665
- endcapB = Topology.Rotate(endcapB, origin=zzz, axis=[0, 0, 1], angle=phi+180)
1666
- endcapB = Topology.Translate(endcapB, origin.X(), origin.Y(), origin.Z())
1667
- return {'pipe': pipe, 'endcapA': endcapA, 'endcapB': endcapB}
1668
-
1669
- @staticmethod
1670
- def Prism(origin: topologic.Vertex = None, width: float = 1, length: float = 1, height: float = 1, uSides: int = 1, vSides: int = 1, wSides: int = 1,
1671
- direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1672
- """
1673
- Description
1674
- ----------
1675
- Creates a prism.
1676
-
1677
- Parameters
1678
- ----------
1679
- origin : topologic.Vertex , optional
1680
- The origin location of the prism. The default is None which results in the prism being placed at (0, 0, 0).
1681
- width : float , optional
1682
- The width of the prism. The default is 1.
1683
- length : float , optional
1684
- The length of the prism. The default is 1.
1685
- height : float , optional
1686
- The height of the prism.
1687
- uSides : int , optional
1688
- The number of sides along the width. The default is 1.
1689
- vSides : int , optional
1690
- The number of sides along the length. The default is 1.
1691
- wSides : int , optional
1692
- The number of sides along the height. The default is 1.
1693
- direction : list , optional
1694
- The vector representing the up direction of the prism. The default is [0, 0, 1].
1695
- placement : str , optional
1696
- 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".
1697
- tolerance : float , optional
1698
- The desired tolerance. The default is 0.0001.
1699
-
1700
- Returns
1701
- -------
1702
- topologic.Cell
1703
- The created prism.
1704
-
1705
- """
1706
- def sliceCell(cell, width, length, height, uSides, vSides, wSides):
1707
- origin = cell.Centroid()
1708
- shells = []
1709
- _ = cell.Shells(None, shells)
1710
- shell = shells[0]
1711
- wRect = Wire.Rectangle(origin=origin, width=width*1.2, length=length*1.2, direction=[0, 0, 1], placement="center")
1712
- sliceFaces = []
1713
- for i in range(1, wSides):
1714
- sliceFaces.append(Topology.Translate(Face.ByWire(wRect, tolerance=tolerance), 0, 0, height/wSides*i - height*0.5))
1715
- uRect = Wire.Rectangle(origin=origin, width=height*1.2, length=length*1.2, direction=[1, 0, 0], placement="center")
1716
- for i in range(1, uSides):
1717
- sliceFaces.append(Topology.Translate(Face.ByWire(uRect, tolerance=tolerance), width/uSides*i - width*0.5, 0, 0))
1718
- vRect = Wire.Rectangle(origin=origin, width=height*1.2, length=width*1.2, direction=[0, 1, 0], placement="center")
1719
- for i in range(1, vSides):
1720
- sliceFaces.append(Topology.Translate(Face.ByWire(vRect, tolerance=tolerance), 0, length/vSides*i - length*0.5, 0))
1721
- if len(sliceFaces) > 0:
1722
- sliceCluster = topologic.Cluster.ByTopologies(sliceFaces)
1723
- shell = Topology.Slice(topologyA=shell, topologyB=sliceCluster, tranDict=False, tolerance=tolerance)
1724
- return Cell.ByShell(shell)
1725
- return cell
1726
-
1727
- from topologicpy.Vertex import Vertex
1728
- from topologicpy.Face import Face
1729
- from topologicpy.Topology import Topology
1730
-
1731
- if not origin:
1732
- origin = Vertex.ByCoordinates(0, 0, 0)
1733
- if not isinstance(origin, topologic.Vertex):
1734
- print("Cell.Prism - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1735
- return None
1736
- xOffset = 0
1737
- yOffset = 0
1738
- zOffset = 0
1739
- if placement.lower() == "center":
1740
- zOffset = -height*0.5
1741
- elif placement.lower() == "lowerleft":
1742
- xOffset = width*0.5
1743
- yOffset = length*0.5
1744
- vb1 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset)
1745
- vb2 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset)
1746
- vb3 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset)
1747
- vb4 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset)
1748
-
1749
- baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
1750
- baseFace = Face.ByWire(baseWire, tolerance=tolerance)
1751
-
1752
- prism = Cell.ByThickenedFace(baseFace, thickness=height, bothSides = False)
1753
-
1754
- if uSides > 1 or vSides > 1 or wSides > 1:
1755
- prism = sliceCell(prism, width, length, height, uSides, vSides, wSides)
1756
- prism = Topology.Orient(prism, origin=origin, dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1757
- return prism
1758
-
1759
- @staticmethod
1760
- def RemoveCollinearEdges(cell: topologic.Cell, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
1761
- """
1762
- Removes any collinear edges in the input cell.
1763
-
1764
- Parameters
1765
- ----------
1766
- cell : topologic.Cell
1767
- The input cell.
1768
- angTolerance : float , optional
1769
- The desired angular tolerance. The default is 0.1.
1770
- tolerance : float , optional
1771
- The desired tolerance. The default is 0.0001.
1772
-
1773
- Returns
1774
- -------
1775
- topologic.Cell
1776
- The created cell without any collinear edges.
1777
-
1778
- """
1779
- from topologicpy.Face import Face
1780
-
1781
- if not isinstance(cell, topologic.Cell):
1782
- print("Cell.RemoveCollinearEdges - Error: The input cell parameter is not a valid cell. Returning None.")
1783
- return None
1784
- faces = Cell.Faces(cell)
1785
- clean_faces = []
1786
- for face in faces:
1787
- clean_faces.append(Face.RemoveCollinearEdges(face, angTolerance=angTolerance, tolerance=tolerance))
1788
- return Cell.ByFaces(clean_faces, tolerance=tolerance)
1789
-
1790
- @staticmethod
1791
- def Roof(face, angle: float = 45, epsilon: float = 0.01 , tolerance: float = 0.001):
1792
- """
1793
- Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
1794
- This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
1795
-
1796
- Parameters
1797
- ----------
1798
- face : topologic.Face
1799
- The input face.
1800
- angle : float , optioal
1801
- The desired angle in degrees of the roof. The default is 45.
1802
- epsilon : float , optional
1803
- The desired epsilon (another form of tolerance for distance from plane). The default is 0.01. (This is set to a larger number as it was found to work better)
1804
- tolerance : float , optional
1805
- The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
1806
-
1807
- Returns
1808
- -------
1809
- cell
1810
- The created roof.
1811
-
1812
- """
1813
- from topologicpy.Shell import Shell
1814
- from topologicpy.Cell import Cell
1815
- from topologicpy.Topology import Topology
1816
-
1817
- shell = Shell.Roof(face=face, angle=angle, epsilon=epsilon, tolerance=tolerance)
1818
- faces = Topology.Faces(shell) + [face]
1819
- cell = Cell.ByFaces(faces, tolerance=tolerance)
1820
- if not cell:
1821
- print("Cell.Roof - Error: Could not create a roof cell. Returning None.")
1822
- return None
1823
- return cell
1824
-
1825
- @staticmethod
1826
- def Sets(cells: list, superCells: list, tolerance: float = 0.0001) -> list:
1827
- """
1828
- 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.
1829
-
1830
- Parameters
1831
- ----------
1832
- inputCells : list
1833
- The list of input cells.
1834
- superCells : list
1835
- The list of super cells.
1836
- tolerance : float , optional
1837
- The desired tolerance. The default is 0.0001.
1838
-
1839
- Returns
1840
- -------
1841
- list
1842
- The classified list of input cells based on their encolsure within the input list of super cells.
1843
-
1844
- """
1845
-
1846
- from topologicpy.Vertex import Vertex
1847
- from topologicpy.Topology import Topology
1848
-
1849
- if not isinstance(cells, list):
1850
- print("Cell.Sets - Error: The input cells parameter is not a valid list. Returning None.")
1851
- return None
1852
- if not isinstance(superCells, list):
1853
- print("Cell.Sets - Error: The input superCells parameter is not a valid list. Returning None.")
1854
- return None
1855
- cells = [c for c in cells if isinstance(c, topologic.Cell)]
1856
- if len(cells) < 1:
1857
- print("Cell.Sets - Error: The input cells parameter does not contain any valid cells. Returning None.")
1858
- return None
1859
- superCells = [c for c in superCells if isinstance(c, topologic.Cell)]
1860
- if len(cells) < 1:
1861
- print("Cell.Sets - Error: The input cells parameter does not contain any valid cells. Returning None.")
1862
- return None
1863
- if len(superCells) == 0:
1864
- cluster = cells[0]
1865
- for i in range(1, len(cells)):
1866
- oldCluster = cluster
1867
- cluster = cluster.Union(cells[i])
1868
- del oldCluster
1869
- superCells = []
1870
- _ = cluster.Cells(None, superCells)
1871
- unused = []
1872
- for i in range(len(cells)):
1873
- unused.append(True)
1874
- sets = []
1875
- for i in range(len(superCells)):
1876
- sets.append([])
1877
- for i in range(len(cells)):
1878
- if unused[i]:
1879
- iv = Topology.InternalVertex(cells[i], tolerance=tolerance)
1880
- for j in range(len(superCells)):
1881
- if (Vertex.IsInternal(iv, superCells[j], tolerance)):
1882
- sets[j].append(cells[i])
1883
- unused[i] = False
1884
- return sets
1885
-
1886
- @staticmethod
1887
- def Shells(cell: topologic.Cell) -> list:
1888
- """
1889
- Returns the shells of the input cell.
1890
-
1891
- Parameters
1892
- ----------
1893
- cell : topologic.Cell
1894
- The input cell.
1895
-
1896
- Returns
1897
- -------
1898
- list
1899
- The list of shells.
1900
-
1901
- """
1902
- if not isinstance(cell, topologic.Cell):
1903
- print("Cell.Shells - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1904
- return None
1905
- shells = []
1906
- _ = cell.Shells(None, shells)
1907
- return shells
1908
-
1909
- @staticmethod
1910
- def Sphere(origin: topologic.Vertex = None, radius: float = 0.5, uSides: int = 16, vSides: int = 8, direction: list = [0, 0, 1],
1911
- placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1912
- """
1913
- Creates a sphere.
1914
-
1915
- Parameters
1916
- ----------
1917
- origin : topologic.Vertex , optional
1918
- The origin location of the sphere. The default is None which results in the sphere being placed at (0, 0, 0).
1919
- radius : float , optional
1920
- The radius of the sphere. The default is 0.5.
1921
- uSides : int , optional
1922
- The number of sides along the longitude of the sphere. The default is 16.
1923
- vSides : int , optional
1924
- The number of sides along the latitude of the sphere. The default is 8.
1925
- direction : list , optional
1926
- The vector representing the up direction of the sphere. The default is [0, 0, 1].
1927
- placement : str , optional
1928
- 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".
1929
- tolerance : float , optional
1930
- The desired tolerance. The default is 0.0001.
1931
-
1932
- Returns
1933
- -------
1934
- topologic.Cell
1935
- The created sphere.
1936
-
1937
- """
1938
-
1939
- from topologicpy.Vertex import Vertex
1940
- from topologicpy.Topology import Topology
1941
-
1942
- if not origin:
1943
- origin = Vertex.ByCoordinates(0, 0, 0)
1944
- if not isinstance(origin, topologic.Vertex):
1945
- print("Cell.Sphere - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1946
- return None
1947
- c = Wire.Circle(origin=Vertex.Origin(), radius=radius, sides=vSides, fromAngle=-90, toAngle=90, close=False, direction=[0, 0, 1], placement="center")
1948
- c = Topology.Rotate(c, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
1949
- sphere = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
1950
- if sphere.Type() == topologic.CellComplex.Type():
1951
- sphere = sphere.ExternalBoundary()
1952
- if sphere.Type() == topologic.Shell.Type():
1953
- sphere = topologic.Cell.ByShell(sphere)
1954
- if placement.lower() == "bottom":
1955
- sphere = Topology.Translate(sphere, 0, 0, radius)
1956
- elif placement.lower() == "lowerleft":
1957
- sphere = Topology.Translate(sphere, radius, radius, radius)
1958
- sphere = Topology.Orient(sphere, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1959
- sphere = Topology.Place(sphere, originA=Vertex.Origin(), originB=origin)
1960
- return sphere
1961
-
1962
- @staticmethod
1963
- def SurfaceArea(cell: topologic.Cell, mantissa: int = 6) -> float:
1964
- """
1965
- Returns the surface area of the input cell.
1966
-
1967
- Parameters
1968
- ----------
1969
- cell : topologic.Cell
1970
- The cell.
1971
- mantissa : int , optional
1972
- The desired length of the mantissa. The default is 6.
1973
-
1974
- Returns
1975
- -------
1976
- area : float
1977
- The surface area of the input cell.
1978
-
1979
- """
1980
- return Cell.Area(cell=cell, mantissa=mantissa)
1981
-
1982
- @staticmethod
1983
- def Tetrahedron(origin: topologic.Vertex = None, radius: float = 0.5,
1984
- direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1985
- """
1986
- Description
1987
- ----------
1988
- Creates a tetrahedron. See https://en.wikipedia.org/wiki/Tetrahedron.
1989
-
1990
- Parameters
1991
- ----------
1992
- origin : topologic.Vertex , optional
1993
- The origin location of the tetrahedron. The default is None which results in the tetrahedron being placed at (0, 0, 0).
1994
- radius : float , optional
1995
- The radius of the tetrahedron's circumscribed sphere. The default is 0.5.
1996
- direction : list , optional
1997
- The vector representing the up direction of the tetrahedron. The default is [0, 0, 1].
1998
- placement : str , optional
1999
- The description of the placement of the origin of the tetrahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
2000
- tolerance : float , optional
2001
- The desired tolerance. The default is 0.0001.
2002
-
2003
- Returns
2004
- -------
2005
- topologic.Cell
2006
- The created tetrahedron.
2007
-
2008
- """
2009
-
2010
- from topologicpy.Vertex import Vertex
2011
- from topologicpy.Wire import Wire
2012
- from topologicpy.Face import Face
2013
- from topologicpy.Topology import Topology
2014
- import math
2015
-
2016
- if not origin:
2017
- origin = Vertex.ByCoordinates(0, 0, 0)
2018
- if not isinstance(origin, topologic.Vertex):
2019
- print("Cell.Tetrahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
2020
- return None
2021
-
2022
- vb1 = Vertex.ByCoordinates(math.sqrt(8/9), 0, -1/3)
2023
- vb2 = Vertex.ByCoordinates(-math.sqrt(2/9), math.sqrt(2/3), -1/3)
2024
- vb3 = Vertex.ByCoordinates(-math.sqrt(2/9), -math.sqrt(2/3), -1/3)
2025
- vb4 = Vertex.ByCoordinates(0, 0, 1)
2026
- f1 = Face.ByVertices([vb1, vb2, vb3])
2027
- f2 = Face.ByVertices([vb4, vb1, vb2])
2028
- f3 = Face.ByVertices([vb4, vb2, vb3])
2029
- f4 = Face.ByVertices([vb4, vb3, vb1])
2030
- tetrahedron = Cell.ByFaces([f1, f2, f3, f4])
2031
- tetrahedron = Topology.Scale(tetrahedron, origin=Vertex.Origin(), x=0.5, y=0.5, z=0.5)
2032
- tetrahedron = Topology.Scale(tetrahedron, origin=Vertex.Origin(), x=radius/0.5, y=radius/0.5, z=radius/0.5)
2033
-
2034
- if placement.lower() == "lowerleft":
2035
- tetrahedron = Topology.Translate(tetrahedron, radius, radius, radius)
2036
- elif placement.lower() == "bottom":
2037
- tetrahedron = Topology.Translate(tetrahedron, 0, 0, radius)
2038
- tetrahedron = Topology.Place(tetrahedron, originA=Vertex.Origin(), originB=origin)
2039
- tetrahedron = Topology.Orient(tetrahedron, origin=origin, dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
2040
- return tetrahedron
2041
-
2042
- @staticmethod
2043
- 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:
2044
- """
2045
- Creates a torus.
2046
-
2047
- Parameters
2048
- ----------
2049
- origin : topologic.Vertex , optional
2050
- The origin location of the torus. The default is None which results in the torus being placed at (0, 0, 0).
2051
- majorRadius : float , optional
2052
- The major radius of the torus. The default is 0.5.
2053
- minorRadius : float , optional
2054
- The minor radius of the torus. The default is 0.1.
2055
- uSides : int , optional
2056
- The number of sides along the longitude of the torus. The default is 16.
2057
- vSides : int , optional
2058
- The number of sides along the latitude of the torus. The default is 8.
2059
- direction : list , optional
2060
- The vector representing the up direction of the torus. The default is [0, 0, 1].
2061
- placement : str , optional
2062
- 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".
2063
- tolerance : float , optional
2064
- The desired tolerance. The default is 0.0001.
2065
-
2066
- Returns
2067
- -------
2068
- topologic.Cell
2069
- The created torus.
2070
-
2071
- """
2072
-
2073
- from topologicpy.Vertex import Vertex
2074
- from topologicpy.Topology import Topology
2075
-
2076
- if not origin:
2077
- origin = Vertex.ByCoordinates(0, 0, 0)
2078
- if not isinstance(origin, topologic.Vertex):
2079
- print("Cell.Torus - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
2080
- return None
2081
- c = Wire.Circle(origin=Vertex.Origin(), radius=minorRadius, sides=vSides, fromAngle=0, toAngle=360, close=False, direction=[0, 1, 0], placement="center")
2082
- c = Topology.Translate(c, abs(majorRadius-minorRadius), 0, 0)
2083
- torus = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
2084
- if torus.Type() == topologic.Shell.Type():
2085
- torus = topologic.Cell.ByShell(torus)
2086
- if placement.lower() == "bottom":
2087
- torus = Topology.Translate(torus, 0, 0, minorRadius)
2088
- elif placement.lower() == "lowerleft":
2089
- torus = Topology.Translate(torus, majorRadius, majorRadius, minorRadius)
2090
-
2091
- torus = Topology.Orient(torus, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
2092
- torus = Topology.Place(torus, originA=Vertex.Origin(), originB=origin)
2093
- return torus
2094
-
2095
- @staticmethod
2096
- def Vertices(cell: topologic.Cell) -> list:
2097
- """
2098
- Returns the vertices of the input cell.
2099
-
2100
- Parameters
2101
- ----------
2102
- cell : topologic.Cell
2103
- The input cell.
2104
-
2105
- Returns
2106
- -------
2107
- list
2108
- The list of vertices.
2109
-
2110
- """
2111
- if not isinstance(cell, topologic.Cell):
2112
- print("Cell.Vertices - Error: The input cell parameter is not a valid topologic cell. Returning None.")
2113
- return None
2114
- vertices = []
2115
- _ = cell.Vertices(None, vertices)
2116
- return vertices
2117
-
2118
- @staticmethod
2119
- def Volume(cell: topologic.Cell, mantissa: int = 6) -> float:
2120
- """
2121
- Returns the volume of the input cell.
2122
-
2123
- Parameters
2124
- ----------
2125
- cell : topologic.Cell
2126
- The input cell.
2127
- manitssa: int , optional
2128
- The desired length of the mantissa. The default is 6.
2129
-
2130
- Returns
2131
- -------
2132
- float
2133
- The volume of the input cell.
2134
-
2135
- """
2136
- if not isinstance(cell, topologic.Cell):
2137
- print("Cell.Volume - Error: The input cell parameter is not a valid topologic cell. Returning None.")
2138
- return None
2139
- volume = None
2140
- try:
2141
- volume = round(topologic.CellUtility.Volume(cell), mantissa)
2142
- except:
2143
- print("Cell.Volume - Error: Could not compute the volume of the input cell. Returning None.")
2144
- volume = None
2145
- return volume
2146
-
2147
- @staticmethod
2148
- def Wires(cell: topologic.Cell) -> list:
2149
- """
2150
- Returns the wires of the input cell.
2151
-
2152
- Parameters
2153
- ----------
2154
- cell : topologic.Cell
2155
- The input cell.
2156
-
2157
- Returns
2158
- -------
2159
- list
2160
- The list of wires.
2161
-
2162
- """
2163
- if not isinstance(cell, topologic.Cell):
2164
- print("Cell.Wires - Error: The input cell parameter is not a valid topologic cell. Returning None.")
2165
- return None
2166
- wires = []
2167
- _ = cell.Wires(None, wires)
2168
- return wires
2169
-
1
+ # Copyright (C) 2024
2
+ # Wassim Jabi <wassim.jabi@gmail.com>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it under
5
+ # the terms of the GNU Affero General Public License as published by the Free Software
6
+ # Foundation, either version 3 of the License, or (at your option) any later
7
+ # version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful, but WITHOUT
10
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12
+ # details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License along with
15
+ # this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ import topologic_core as topologic
18
+ from topologicpy.Wire import Wire
19
+ from topologicpy.Topology import Topology
20
+ import math
21
+
22
+ class Cell(Topology):
23
+ @staticmethod
24
+ def Area(cell: topologic.Cell, mantissa: int = 6) -> float:
25
+ """
26
+ Returns the surface area of the input cell.
27
+
28
+ Parameters
29
+ ----------
30
+ cell : topologic.Cell
31
+ The cell.
32
+ mantissa : int , optional
33
+ The desired length of the mantissa. The default is 6.
34
+
35
+ Returns
36
+ -------
37
+ float
38
+ The surface area of the input cell.
39
+
40
+ """
41
+ from topologicpy.Face import Face
42
+
43
+ faces = []
44
+ _ = cell.Faces(None, faces)
45
+ area = 0.0
46
+ for aFace in faces:
47
+ area = area + Face.Area(aFace)
48
+ return round(area, mantissa)
49
+
50
+ @staticmethod
51
+ def Box(origin: topologic.Vertex = None,
52
+ width: float = 1, length: float = 1, height: float = 1,
53
+ uSides: int = 1, vSides: int = 1, wSides: int = 1,
54
+ direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
55
+ """
56
+ Creates a box.
57
+
58
+ Parameters
59
+ ----------
60
+ origin : topologic.Vertex , optional
61
+ The origin location of the box. The default is None which results in the box being placed at (0, 0, 0).
62
+ width : float , optional
63
+ The width of the box. The default is 1.
64
+ length : float , optional
65
+ The length of the box. The default is 1.
66
+ height : float , optional
67
+ The height of the box.
68
+ uSides : int , optional
69
+ The number of sides along the width. The default is 1.
70
+ vSides : int , optional
71
+ The number of sides along the length. The default is 1.
72
+ wSides : int , optional
73
+ The number of sides along the height. The default is 1.
74
+ direction : list , optional
75
+ The vector representing the up direction of the box. The default is [0, 0, 1].
76
+ placement : str , optional
77
+ 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".
78
+ tolerance : float , optional
79
+ The desired tolerance. The default is 0.0001.
80
+
81
+ Returns
82
+ -------
83
+ topologic.Cell
84
+ The created box.
85
+
86
+ """
87
+ return Cell.Prism(origin=origin, width=width, length=length, height=height,
88
+ uSides=uSides, vSides=vSides, wSides=wSides,
89
+ direction=direction, placement=placement, tolerance=tolerance)
90
+
91
+ @staticmethod
92
+ def ByFaces(faces: list, planarize: bool = False, tolerance: float = 0.0001, silent=False) -> topologic.Cell:
93
+ """
94
+ Creates a cell from the input list of faces.
95
+
96
+ Parameters
97
+ ----------
98
+ faces : list
99
+ The input list of faces.
100
+ planarize : bool, optional
101
+ If set to True, the input faces are planarized before building the cell. Otherwise, they are not. The default is False.
102
+ tolerance : float , optional
103
+ The desired tolerance. The default is 0.0001.
104
+ silent : bool , optional
105
+ If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
106
+
107
+ Returns
108
+ -------
109
+ topologic.Cell
110
+ The created cell.
111
+
112
+ """
113
+ from topologicpy.Vertex import Vertex
114
+ from topologicpy.Wire import Wire
115
+ from topologicpy.Face import Face
116
+ from topologicpy.Topology import Topology
117
+
118
+ if not isinstance(faces, list):
119
+ if not silent:
120
+ print("Cell.ByFaces - Error: The input faces parameter is not a valid list. Returning None.")
121
+ return None
122
+ faceList = [x for x in faces if isinstance(x, topologic.Face)]
123
+ if len(faceList) < 1:
124
+ if not silent:
125
+ print("Cell.ByFaces - Error: The input faces parameter does not contain valid faces. Returning None.")
126
+ return None
127
+ # Try the default method
128
+ cell = topologic.Cell.ByFaces(faceList, tolerance)
129
+ if isinstance(cell, topologic.Cell):
130
+ return cell
131
+
132
+ # Fuse all the vertices first and rebuild the faces
133
+ all_vertices = []
134
+ wires = []
135
+ for f in faceList:
136
+ w = Face.Wire(f)
137
+ wires.append(w)
138
+ all_vertices += Topology.Vertices(w)
139
+ all_vertices = Vertex.Fuse(all_vertices, tolerance=tolerance)
140
+ new_faces = []
141
+ for w in wires:
142
+ face_vertices = []
143
+ for v in Topology.Vertices(w):
144
+ index = Vertex.Index(v, all_vertices, tolerance=tolerance)
145
+ if not index == None:
146
+ face_vertices.append(all_vertices[index])
147
+ new_w = Wire.ByVertices(face_vertices)
148
+ if isinstance(new_w, topologic.Wire):
149
+ new_f = Face.ByWire(new_w, silent=True)
150
+ if isinstance(new_f, topologic.Face):
151
+ new_faces.append(new_f)
152
+ elif isinstance(new_f, list):
153
+ new_faces += new_f
154
+ faceList = new_faces
155
+ planarizedList = []
156
+ enlargedList = []
157
+ if planarize:
158
+ planarizedList = [Face.Planarize(f, tolerance=tolerance) for f in faceList]
159
+ enlargedList = [Face.ByOffset(f, offset=-tolerance*10) for f in planarizedList]
160
+ cell = topologic.Cell.ByFaces(enlargedList, tolerance)
161
+ faceList = Topology.SubTopologies(cell, subTopologyType="face")
162
+ finalFaces = []
163
+ for f in faceList:
164
+ centroid = Topology.Centroid(f)
165
+ n = Face.Normal(f)
166
+ v = Topology.Translate(centroid, n[0]*0.01, n[1]*0.01, n[2]*0.01)
167
+ if not Vertex.IsInternal(v, cell):
168
+ finalFaces.append(f)
169
+ finalFinalFaces = []
170
+ for f in finalFaces:
171
+ vertices = Face.Vertices(f)
172
+ w = Wire.Cycles(Face.ExternalBoundary(f), maxVertices=len(vertices))[0]
173
+ f1 = Face.ByWire(w, tolerance=tolerance, silent=True)
174
+ if isinstance(f1, topologic.Face):
175
+ finalFinalFaces.append(f1)
176
+ elif isinstance(f1, list):
177
+ finalFinalFaces += f1
178
+ cell = topologic.Cell.ByFaces(finalFinalFaces, tolerance)
179
+ if cell == None:
180
+ if not silent:
181
+ print("Cell.ByFaces - Error: The operation failed. Returning None.")
182
+ return None
183
+ else:
184
+ return cell
185
+ else:
186
+ cell = topologic.Cell.ByFaces(faces, tolerance)
187
+ if cell == None:
188
+ if not silent:
189
+ print("Cell.ByFaces - Error: The operation failed. Returning None.")
190
+ return None
191
+ else:
192
+ return cell
193
+ @staticmethod
194
+ def ByOffset(cell: topologic.Cell, offset: float = 1.0, tolerance: float = 0.0001) -> topologic.Face:
195
+ """
196
+ Creates an offset cell from the input cell.
197
+
198
+ Parameters
199
+ ----------
200
+ cell : topologic.Cell
201
+ The input cell.
202
+ offset : float , optional
203
+ The desired offset distance. The default is 1.0.
204
+ tolerance : float , optional
205
+ The desired tolerance. The default is 0.0001.
206
+
207
+ Returns
208
+ -------
209
+ topologic.Topology
210
+ The created offset topology. WARNING: This method may fail to create a cell if the offset creates self-intersecting faces. Always check the type being returned by this method.
211
+
212
+ """
213
+ from topologicpy.Face import Face
214
+ from topologicpy.Topology import Topology
215
+ from topologicpy.Vector import Vector
216
+
217
+ vertices = Topology.Vertices(cell)
218
+ new_vertices = []
219
+ for v in vertices:
220
+ faces = Topology.SuperTopologies(v, hostTopology=cell, topologyType="face")
221
+ normals = []
222
+ for face in faces:
223
+ normal = Vector.SetMagnitude(Face.Normal(face), offset)
224
+ normals.append(normal)
225
+ sum_normal = Vector.Sum(normals)
226
+ new_v = Topology.TranslateByDirectionDistance(v, direction=sum_normal, distance=Vector.Magnitude(sum_normal))
227
+ new_vertices.append(new_v)
228
+ new_cell = Topology.SelfMerge(Topology.ReplaceVertices(cell, Topology.Vertices(cell), new_vertices), tolerance=tolerance)
229
+ return new_cell
230
+
231
+ @staticmethod
232
+ def ByShell(shell: topologic.Shell, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
233
+ """
234
+ Creates a cell from the input shell.
235
+
236
+ Parameters
237
+ ----------
238
+ shell : topologic.Shell
239
+ The input shell. The shell must be closed for this method to succeed.
240
+ planarize : bool, optional
241
+ 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.
242
+ tolerance : float , optional
243
+ The desired tolerance. The default is 0.0001.
244
+
245
+ Returns
246
+ -------
247
+ topologic.Cell
248
+ The created cell.
249
+
250
+ """
251
+ from topologicpy.Topology import Topology
252
+ if not isinstance(shell, topologic.Shell):
253
+ print("Cell.ByShell - Error: The input shell parameter is not a valid topologic shell. Returning None.")
254
+ return None
255
+ faces = Topology.SubTopologies(shell, subTopologyType="face")
256
+ return Cell.ByFaces(faces, planarize=planarize, tolerance=tolerance)
257
+
258
+ @staticmethod
259
+ def ByThickenedFace(face: topologic.Face, thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
260
+ planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
261
+ """
262
+ Creates a cell by thickening the input face.
263
+
264
+ Parameters
265
+ ----------
266
+ face : topologic.Face
267
+ The input face to be thickened.
268
+ thickness : float , optional
269
+ The desired thickness. The default is 1.0.
270
+ bothSides : bool
271
+ 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.
272
+ reverse : bool
273
+ If True, the cell will be lofted in the opposite direction of the normal to the face. The default is False.
274
+ planarize : bool, optional
275
+ 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.
276
+ tolerance : float , optional
277
+ The desired tolerance. The default is 0.0001.
278
+
279
+ Returns
280
+ -------
281
+ topologic.Cell
282
+ The created cell.
283
+
284
+ """
285
+ from topologicpy.Edge import Edge
286
+ from topologicpy.Face import Face
287
+ from topologicpy.Cluster import Cluster
288
+ from topologicpy.Topology import Topology
289
+
290
+ if not isinstance(face, topologic.Face):
291
+ print("Cell.ByThickenedFace - Error: The input face parameter is not a valid topologic face. Returning None.")
292
+ return None
293
+ if reverse == True and bothSides == False:
294
+ thickness = -thickness
295
+ faceNormal = Face.Normal(face)
296
+ if bothSides:
297
+ bottomFace = Topology.Translate(face, -faceNormal[0]*0.5*thickness, -faceNormal[1]*0.5*thickness, -faceNormal[2]*0.5*thickness)
298
+ topFace = Topology.Translate(face, faceNormal[0]*0.5*thickness, faceNormal[1]*0.5*thickness, faceNormal[2]*0.5*thickness)
299
+ else:
300
+ bottomFace = face
301
+ topFace = Topology.Translate(face, faceNormal[0]*thickness, faceNormal[1]*thickness, faceNormal[2]*thickness)
302
+
303
+ cellFaces = [bottomFace, topFace]
304
+ bottomEdges = []
305
+ _ = bottomFace.Edges(None, bottomEdges)
306
+ for bottomEdge in bottomEdges:
307
+ topEdge = Topology.Translate(bottomEdge, faceNormal[0]*thickness, faceNormal[1]*thickness, faceNormal[2]*thickness)
308
+ sideEdge1 = Edge.ByVertices([bottomEdge.StartVertex(), topEdge.StartVertex()], tolerance=tolerance, silent=True)
309
+ sideEdge2 = Edge.ByVertices([bottomEdge.EndVertex(), topEdge.EndVertex()], tolerance=tolerance, silent=True)
310
+ cellWire = Topology.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]), tolerance=tolerance)
311
+ cellFaces.append(Face.ByWire(cellWire, tolerance=tolerance))
312
+ return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
313
+
314
+ @staticmethod
315
+ def ByThickenedShell(shell: topologic.Shell, direction: list = [0, 0, 1], thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
316
+ planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
317
+ """
318
+ Creates a cell by thickening the input shell. The shell must be open.
319
+
320
+ Parameters
321
+ ----------
322
+ shell : topologic.Shell
323
+ The input shell to be thickened.
324
+ thickness : float , optional
325
+ The desired thickness. The default is 1.0.
326
+ bothSides : bool
327
+ 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.
328
+ reverse : bool
329
+ If True, the cell will be lofted along the opposite of the input direction. The default is False.
330
+ planarize : bool, optional
331
+ 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.
332
+ tolerance : float , optional
333
+ The desired tolerance. The default is 0.0001.
334
+
335
+ Returns
336
+ -------
337
+ topologic.Cell
338
+ The created cell.
339
+
340
+ """
341
+ from topologicpy.Edge import Edge
342
+ from topologicpy.Wire import Wire
343
+ from topologicpy.Face import Face
344
+ from topologicpy.Shell import Shell
345
+ from topologicpy.Cluster import Cluster
346
+ from topologicpy.Topology import Topology
347
+ if not isinstance(shell, topologic.Shell):
348
+ print("Cell.ByThickenedShell - Error: The input shell parameter is not a valid topologic Shell. Returning None.")
349
+ return None
350
+ if reverse == True and bothSides == False:
351
+ thickness = -thickness
352
+ if bothSides:
353
+ bottomShell = Topology.Translate(shell, -direction[0]*0.5*thickness, -direction[1]*0.5*thickness, -direction[2]*0.5*thickness)
354
+ topShell = Topology.Translate(shell, direction[0]*0.5*thickness, direction[1]*0.5*thickness, direction[2]*0.5*thickness)
355
+ else:
356
+ bottomShell = shell
357
+ topShell = Topology.Translate(shell, direction[0]*thickness, direction[1]*thickness, direction[2]*thickness)
358
+ cellFaces = Shell.Faces(bottomShell) + Shell.Faces(topShell)
359
+ bottomWire = Shell.ExternalBoundary(bottomShell, tolerance=tolerance)
360
+ bottomEdges = Wire.Edges(bottomWire)
361
+ for bottomEdge in bottomEdges:
362
+ topEdge = Topology.Translate(bottomEdge, direction[0]*thickness, direction[1]*thickness, direction[2]*thickness)
363
+ sideEdge1 = Edge.ByVertices([Edge.StartVertex(bottomEdge), Edge.StartVertex(topEdge)], tolerance=tolerance, silent=True)
364
+ sideEdge2 = Edge.ByVertices([Edge.EndVertex(bottomEdge), Edge.EndVertex(topEdge)], tolerance=tolerance, silent=True)
365
+ cellWire = Topology.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]), tolerance=tolerance)
366
+ cellFace = Face.ByWire(cellWire, tolerance=tolerance)
367
+ cellFaces.append(cellFace)
368
+ return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
369
+
370
+ @staticmethod
371
+ def ByWires(wires: list, close: bool = False, triangulate: bool = True, planarize: bool = False, mantissa: int = 6, tolerance: float = 0.0001, silent=False) -> topologic.Cell:
372
+ """
373
+ Creates a cell by lofting through the input list of wires.
374
+
375
+ Parameters
376
+ ----------
377
+ wires : topologic.Wire
378
+ The input list of wires.
379
+ close : bool , optional
380
+ 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.
381
+ triangulate : bool , optional
382
+ If set to True, the faces will be triangulated. The default is True.
383
+ mantissa : int , optional
384
+ The desired length of the mantissa. The default is 6.
385
+ tolerance : float , optional
386
+ The desired tolerance. The default is 0.0001.
387
+ silent : bool , optional
388
+ If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
389
+
390
+ Raises
391
+ ------
392
+ Exception
393
+ Raises an exception if the two wires in the list do not have the same number of edges.
394
+
395
+ Returns
396
+ -------
397
+ topologic.Cell
398
+ The created cell.
399
+
400
+ """
401
+ from topologicpy.Edge import Edge
402
+ from topologicpy.Wire import Wire
403
+ from topologicpy.Face import Face
404
+ from topologicpy.Shell import Shell
405
+ from topologicpy.Topology import Topology
406
+
407
+ if not isinstance(wires, list):
408
+ if not silent:
409
+ print("Cell.ByWires - Error: The input wires parameter is not a valid list. Returning None.")
410
+ return None
411
+ wires = [w for w in wires if isinstance(w, topologic.Wire)]
412
+ if len(wires) < 2:
413
+ if not silent:
414
+ print("Cell.ByWires - Error: The input wires parameter contains less than two valid topologic wires. Returning None.")
415
+ return None
416
+ faces = [Face.ByWire(wires[0], tolerance=tolerance), Face.ByWire(wires[-1], tolerance=tolerance)]
417
+ if close == True:
418
+ faces.append(Face.ByWire(wires[0], tolerance=tolerance))
419
+ if triangulate == True:
420
+ triangles = []
421
+ for face in faces:
422
+ if len(Topology.Vertices(face)) > 3:
423
+ triangles += Face.Triangulate(face, tolerance=tolerance)
424
+ else:
425
+ triangles += [face]
426
+ faces = triangles
427
+ for i in range(len(wires)-1):
428
+ wire1 = wires[i]
429
+ wire2 = wires[i+1]
430
+ w1_edges = []
431
+ _ = wire1.Edges(None, w1_edges)
432
+ w2_edges = []
433
+ _ = wire2.Edges(None, w2_edges)
434
+ if len(w1_edges) != len(w2_edges):
435
+ if not silent:
436
+ print("Cell.ByWires - Error: The input wires parameter contains wires with different number of edges. Returning None.")
437
+ return None
438
+ if triangulate == True:
439
+ for j in range (len(w1_edges)):
440
+ e1 = w1_edges[j]
441
+ e2 = w2_edges[j]
442
+ e3 = None
443
+ e4 = None
444
+ try:
445
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
446
+ except:
447
+ try:
448
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
449
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e4], tolerance=tolerance), tolerance=tolerance))
450
+ except:
451
+ pass
452
+ try:
453
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
454
+ except:
455
+ try:
456
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
457
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e3], tolerance=tolerance), tolerance=tolerance))
458
+ except:
459
+ pass
460
+ if e3 and e4:
461
+ e5 = Edge.ByVertices([e1.StartVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
462
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e5, e4], tolerance=tolerance), tolerance=tolerance))
463
+ faces.append(Face.ByWire(Wire.ByEdges([e2, e5, e3], tolerance=tolerance), tolerance=tolerance))
464
+ else:
465
+ for j in range (len(w1_edges)):
466
+ e1 = w1_edges[j]
467
+ e2 = w2_edges[j]
468
+ e3 = None
469
+ e4 = None
470
+ try:
471
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
472
+ except:
473
+ try:
474
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
475
+ except:
476
+ pass
477
+ try:
478
+ e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=True)
479
+ except:
480
+ try:
481
+ e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=True)
482
+ except:
483
+ pass
484
+ if e3 and e4:
485
+ try:
486
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2, e3], tolerance=tolerance), tolerance=tolerance))
487
+ except:
488
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2, e4], tolerance=tolerance), tolerance=tolerance))
489
+ elif e3:
490
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2], tolerance=tolerance), tolerance=tolerance))
491
+ elif e4:
492
+ faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2], tolerance=tolerance), tolerance=tolerance))
493
+ cell = Cell.ByFaces(faces, planarize=planarize, tolerance=tolerance, silent=silent)
494
+ if not cell:
495
+ shell = Shell.ByFaces(faces, tolerance=tolerance)
496
+ if isinstance(shell, topologic.Shell):
497
+ geom = Topology.Geometry(shell, mantissa=mantissa)
498
+ cell = Topology.ByGeometry(geom['vertices'], geom['edges'], geom['faces'])
499
+ if not isinstance(cell, topologic.Cell):
500
+ print("Cell.ByWires - Error: Could not create a cell. Returning None.")
501
+ return None
502
+ return cell
503
+
504
+ @staticmethod
505
+ def ByWiresCluster(cluster: topologic.Cluster, close: bool = False, triangulate: bool = True, planarize: bool = False, tolerance: float = 0.0001) -> topologic.Cell:
506
+ """
507
+ Creates a cell by lofting through the input cluster of wires.
508
+
509
+ Parameters
510
+ ----------
511
+ cluster : topologic.Cluster
512
+ The input Cluster of wires.
513
+ close : bool , optional
514
+ 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.
515
+ triangulate : bool , optional
516
+ If set to True, the faces will be triangulated. The default is True.
517
+ tolerance : float , optional
518
+ The desired tolerance. The default is 0.0001.
519
+
520
+ Raises
521
+ ------
522
+ Exception
523
+ Raises an exception if the two wires in the list do not have the same number of edges.
524
+
525
+ Returns
526
+ -------
527
+ topologic.Cell
528
+ The created cell.
529
+
530
+ """
531
+ if not isinstance(cluster, topologic.Cluster):
532
+ print("Cell.ByWiresCluster - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
533
+ return None
534
+ wires = []
535
+ _ = cluster.Wires(None, wires)
536
+ return Cell.ByWires(wires, close=close, triangulate=triangulate, planarize=planarize, tolerance=tolerance)
537
+
538
+ @staticmethod
539
+ def Capsule(origin: topologic.Vertex = None, radius: float = 0.25, height: float = 1, uSides: int = 16, vSidesEnds:int = 8, vSidesMiddle: int = 1, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
540
+ """
541
+ Creates a capsule shape. A capsule is a cylinder with hemispherical ends.
542
+
543
+ Parameters
544
+ ----------
545
+ origin : topologic.Vertex , optional
546
+ The location of the origin of the cylinder. The default is None which results in the cylinder being placed at (0, 0, 0).
547
+ radius : float , optional
548
+ The radius of the capsule. The default is 0.25.
549
+ height : float , optional
550
+ The height of the capsule. The default is 1.
551
+ uSides : int , optional
552
+ The number of circle segments of the capsule. The default is 16.
553
+ vSidesEnds : int , optional
554
+ The number of vertical segments of the end hemispheres. The default is 8.
555
+ vSidesMiddle : int , optional
556
+ The number of vertical segments of the middle cylinder. The default is 1.
557
+ direction : list , optional
558
+ The vector representing the up direction of the capsule. The default is [0, 0, 1].
559
+ placement : str , optional
560
+ The description of the placement of the origin of the capsule. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "bottom".
561
+ tolerance : float , optional
562
+ The desired tolerance. The default is 0.0001.
563
+
564
+ Returns
565
+ -------
566
+ topologic.Cell
567
+ The created cell.
568
+
569
+ """
570
+ from topologicpy.Topology import Topology
571
+ from topologicpy.Cell import Cell
572
+ from topologicpy.Vertex import Vertex
573
+ if not origin:
574
+ origin = Vertex.ByCoordinates(0, 0, 0)
575
+ if not isinstance(origin, topologic.Vertex):
576
+ print("Cell.Capsule - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
577
+ return None
578
+ cyl_height = height - radius*2
579
+ if cyl_height <= 0:
580
+ capsule = Cell.Sphere(origin=Vertex.Origin(), radius=radius, uSides= uSides, vSides=vSidesEnds*2)
581
+ else:
582
+ cyl = Cell.Cylinder(origin=Vertex.Origin(),
583
+ radius=radius,
584
+ height=cyl_height,
585
+ uSides=uSides, vSides=vSidesMiddle, direction=[0, 0, 1], placement="center", tolerance=tolerance)
586
+ o1 = Vertex.ByCoordinates(0, 0, cyl_height*0.5)
587
+ o2 = Vertex.ByCoordinates(0, 0, -cyl_height*0.5)
588
+ s1 = Cell.Sphere(origin=o1, radius=radius, uSides=uSides, vSides=vSidesEnds*2, tolerance=tolerance)
589
+ s2 = Cell.Sphere(origin=o2, radius=radius, uSides=uSides, vSides=vSidesEnds*2, tolerance=tolerance)
590
+ capsule = Topology.Union(cyl, s1, tolerance=tolerance)
591
+ capsule = Topology.Union(capsule, s2, tolerance=tolerance)
592
+ if placement == "bottom":
593
+ capsule = Topology.Translate(capsule, 0, 0, height/2)
594
+ if placement == "lowerleft":
595
+ capsule = Topology.Translate(capsule, 0, 0, height/2)
596
+ capsule = Topology.Translate(capsule, radius, radius)
597
+
598
+ capsule = Topology.Orient(capsule, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
599
+ capsule = Topology.Place(capsule, originA=Vertex.Origin(), originB=origin)
600
+ return capsule
601
+
602
+ @staticmethod
603
+ def Compactness(cell: topologic.Cell, reference = "sphere", mantissa: int = 6) -> float:
604
+ """
605
+ Returns the compactness measure of the input cell. If the reference is "sphere", this is also known as 'sphericity' (https://en.wikipedia.org/wiki/Sphericity).
606
+
607
+ Parameters
608
+ ----------
609
+ cell : topologic.Cell
610
+ The input cell.
611
+ reference : str , optional
612
+ The desired reference to which to compare this compactness. The options are "sphere" and "cube". It is case insensitive. The default is "sphere".
613
+ mantissa : int , optional
614
+ The desired length of the mantissa. The default is 6.
615
+
616
+ Returns
617
+ -------
618
+ float
619
+ The compactness of the input cell.
620
+
621
+ """
622
+ from topologicpy.Face import Face
623
+ faces = []
624
+ _ = cell.Faces(None, faces)
625
+ area = 0.0
626
+ for aFace in faces:
627
+ area = area + abs(Face.Area(aFace))
628
+ volume = abs(topologic.CellUtility.Volume(cell))
629
+ compactness = 0
630
+ #From https://en.wikipedia.org/wiki/Sphericity
631
+ if area > 0:
632
+ if reference.lower() == "sphere":
633
+ compactness = (((math.pi)**(1/3))*((6*volume)**(2/3)))/area
634
+ else:
635
+ compactness = 6*(volume**(2/3))/area
636
+ else:
637
+ print("Cell.Compactness - Error: cell surface area is not positive. Returning None.")
638
+ return None
639
+ return round(compactness, mantissa)
640
+
641
+ @staticmethod
642
+ 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],
643
+ dirZ: float = 1, placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
644
+ """
645
+ Creates a cone.
646
+
647
+ Parameters
648
+ ----------
649
+ origin : topologic.Vertex , optional
650
+ The location of the origin of the cone. The default is None which results in the cone being placed at (0, 0, 0).
651
+ baseRadius : float , optional
652
+ The radius of the base circle of the cone. The default is 0.5.
653
+ topRadius : float , optional
654
+ The radius of the top circle of the cone. The default is 0.
655
+ height : float , optional
656
+ The height of the cone. The default is 1.
657
+ sides : int , optional
658
+ The number of sides of the cone. The default is 16.
659
+ direction : list , optional
660
+ The vector representing the up direction of the cone. The default is [0, 0, 1].
661
+ placement : str , optional
662
+ 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".
663
+ tolerance : float , optional
664
+ The desired tolerance. The default is 0.0001.
665
+
666
+ Returns
667
+ -------
668
+ topologic.Cell
669
+ The created cone.
670
+
671
+ """
672
+ from topologicpy.Vertex import Vertex
673
+ from topologicpy.Wire import Wire
674
+ from topologicpy.Face import Face
675
+ from topologicpy.Shell import Shell
676
+ from topologicpy.Cluster import Cluster
677
+ from topologicpy.Topology import Topology
678
+ def createCone(baseWire, topWire, baseVertex, topVertex, tolerance):
679
+ if baseWire == None and topWire == None:
680
+ raise Exception("Cell.Cone - Error: Both radii of the cone cannot be zero at the same time")
681
+ elif baseWire == None:
682
+ apex = baseVertex
683
+ wire = topWire
684
+ elif topWire == None:
685
+ apex = topVertex
686
+ wire = baseWire
687
+ else:
688
+ return topologic.CellUtility.ByLoft([baseWire, topWire])
689
+ vertices = []
690
+ _ = wire.Vertices(None,vertices)
691
+ faces = [Face.ByWire(wire, tolerance=tolerance)]
692
+ for i in range(0, len(vertices)-1):
693
+ w = Wire.ByVertices([apex, vertices[i], vertices[i+1]])
694
+ f = Face.ByWire(w, tolerance=tolerance)
695
+ faces.append(f)
696
+ w = Wire.ByVertices([apex, vertices[-1], vertices[0]])
697
+ f = Face.ByWire(w, tolerance=tolerance)
698
+ faces.append(f)
699
+ return Cell.ByFaces(faces, tolerance=tolerance)
700
+ if not origin:
701
+ origin = Vertex.ByCoordinates(0, 0, 0)
702
+ if not isinstance(origin, topologic.Vertex):
703
+ print("Cell.Cone - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
704
+ return None
705
+ xOffset = 0
706
+ yOffset = 0
707
+ zOffset = 0
708
+ if placement.lower() == "center":
709
+ xOffset = 0
710
+ yOffset = 0
711
+ zOffset = -height*0.5
712
+ elif placement.lower() == "lowerleft":
713
+ xOffset = max(baseRadius, topRadius)
714
+ yOffset = max(baseRadius, topRadius)
715
+ zOffset = 0
716
+
717
+ baseZ = origin.Z() + zOffset
718
+ topZ = origin.Z() + zOffset + height
719
+ baseV = []
720
+ topV = []
721
+ for i in range(uSides):
722
+ angle = math.radians(360/uSides)*i
723
+ if baseRadius > 0:
724
+ baseX = math.cos(angle)*baseRadius + origin.X() + xOffset
725
+ baseY = math.sin(angle)*baseRadius + origin.Y() + yOffset
726
+ baseZ = origin.Z() + zOffset
727
+ baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ))
728
+ if topRadius > 0:
729
+ topX = math.cos(angle)*topRadius + origin.X() + xOffset
730
+ topY = math.sin(angle)*topRadius + origin.Y() + yOffset
731
+ topV.append(Vertex.ByCoordinates(topX,topY,topZ))
732
+ if baseRadius > 0:
733
+ baseWire = Wire.ByVertices(baseV)
734
+ else:
735
+ baseWire = None
736
+ if topRadius > 0:
737
+ topWire = Wire.ByVertices(topV)
738
+ else:
739
+ topWire = None
740
+ baseVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset)
741
+ topVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset+height)
742
+ cone = createCone(baseWire, topWire, baseVertex, topVertex, tolerance)
743
+ if cone == None:
744
+ print("Cell.Cone - Error: Could not create a cone. Returning None.")
745
+ return None
746
+
747
+ if vSides > 1:
748
+ cutting_planes = []
749
+ baseX = origin.X() + xOffset
750
+ baseY = origin.Y() + yOffset
751
+ size = max(baseRadius, topRadius)*3
752
+ for i in range(1, vSides):
753
+ baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i
754
+ tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ)
755
+ cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size), tolerance=tolerance))
756
+ cutting_planes_cluster = Cluster.ByTopologies(cutting_planes)
757
+ shell = Cell.Shells(cone)[0]
758
+ shell = shell.Slice(cutting_planes_cluster)
759
+ cone = Cell.ByShell(shell)
760
+ cone = Topology.Orient(cone, origin=origin, dirA=[0, 0, 1], dirB=direction)
761
+ return cone
762
+
763
+ @staticmethod
764
+ def ContainmentStatus(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> int:
765
+ """
766
+ Returns the containment status of the input vertex in relationship to the input cell
767
+
768
+ Parameters
769
+ ----------
770
+ cell : topologic.Cell
771
+ The input cell.
772
+ vertex : topologic.Vertex
773
+ The input vertex.
774
+ tolerance : float , optional
775
+ The desired tolerance. The default is 0.0001.
776
+
777
+ Returns
778
+ -------
779
+ int
780
+ Returns 0 if the vertex is inside, 1 if it is on the boundary of, and 2 if it is outside the input cell.
781
+
782
+ """
783
+ if not isinstance(cell, topologic.Cell):
784
+ print("Cell.ContainmentStatus - Error: The input cell parameter is not a valid topologic cell. Returning None.")
785
+ return None
786
+ if not isinstance(vertex, topologic.Vertex):
787
+ print("Cell.ContainmentStatus - Error: The input vertex parameter is not a valid topologic vertex. Returning None.")
788
+ return None
789
+ try:
790
+ status = topologic.CellUtility.Contains(cell, vertex, tolerance)
791
+ if status == 0:
792
+ return 0
793
+ elif status == 1:
794
+ return 1
795
+ else:
796
+ return 2
797
+ except:
798
+ print("Cell.ContainmentStatus - Error: Could not determine containment status. Returning None.")
799
+ return None
800
+
801
+ @staticmethod
802
+ def Cylinder(origin: topologic.Vertex = None, radius: float = 0.5, height: float = 1, uSides: int = 16, vSides: int = 1, direction: list = [0, 0, 1],
803
+ placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
804
+ """
805
+ Creates a cylinder.
806
+
807
+ Parameters
808
+ ----------
809
+ origin : topologic.Vertex , optional
810
+ The location of the origin of the cylinder. The default is None which results in the cylinder being placed at (0, 0, 0).
811
+ radius : float , optional
812
+ The radius of the cylinder. The default is 0.5.
813
+ height : float , optional
814
+ The height of the cylinder. The default is 1.
815
+ uSides : int , optional
816
+ The number of circle segments of the cylinder. The default is 16.
817
+ vSides : int , optional
818
+ The number of vertical segments of the cylinder. The default is 1.
819
+ direction : list , optional
820
+ The vector representing the up direction of the cylinder. The default is [0, 0, 1].
821
+ placement : str , optional
822
+ 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".
823
+ tolerance : float , optional
824
+ The desired tolerance. The default is 0.0001.
825
+
826
+ Returns
827
+ -------
828
+ topologic.Cell
829
+ The created cell.
830
+
831
+ """
832
+ from topologicpy.Vertex import Vertex
833
+ from topologicpy.Face import Face
834
+ from topologicpy.CellComplex import CellComplex
835
+ from topologicpy.Cluster import Cluster
836
+ from topologicpy.Topology import Topology
837
+ if not origin:
838
+ origin = Vertex.ByCoordinates(0, 0, 0)
839
+ if not isinstance(origin, topologic.Vertex):
840
+ print("Cell.Cylinder - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
841
+ return None
842
+ xOffset = 0
843
+ yOffset = 0
844
+ zOffset = 0
845
+ if placement.lower() == "center":
846
+ zOffset = -height*0.5
847
+ elif placement.lower() == "lowerleft":
848
+ xOffset = radius
849
+ yOffset = radius
850
+ circle_origin = Vertex.ByCoordinates(origin.X() + xOffset, origin.Y() + yOffset, origin.Z() + zOffset)
851
+
852
+ baseWire = Wire.Circle(origin=circle_origin, radius=radius, sides=uSides, fromAngle=0, toAngle=360, close=True, direction=[0, 0, 1], placement="center", tolerance=tolerance)
853
+ baseFace = Face.ByWire(baseWire, tolerance=tolerance)
854
+ cylinder = Cell.ByThickenedFace(face=baseFace, thickness=height, bothSides=False, reverse=True,
855
+ tolerance=tolerance)
856
+ if vSides > 1:
857
+ cutting_planes = []
858
+ baseX = origin.X() + xOffset
859
+ baseY = origin.Y() + yOffset
860
+ size = radius*3
861
+ for i in range(1, vSides):
862
+ baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i
863
+ tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ)
864
+ cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size), tolerance=tolerance))
865
+ cutting_planes_cluster = Cluster.ByTopologies(cutting_planes)
866
+ cylinder = CellComplex.ExternalBoundary(cylinder.Slice(cutting_planes_cluster))
867
+
868
+ cylinder = Topology.Orient(cylinder, origin=origin, dirA=[0, 0, 1], dirB=direction)
869
+ return cylinder
870
+
871
+ @staticmethod
872
+ def Decompose(cell: topologic.Cell, tiltAngle: float = 10, tolerance: float = 0.0001) -> dict:
873
+ """
874
+ Decomposes the input cell into its logical components. This method assumes that the positive Z direction is UP.
875
+
876
+ Parameters
877
+ ----------
878
+ cell : topologic.Cell
879
+ the input cell.
880
+ tiltAngle : float , optional
881
+ 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.
882
+ tolerance : float , optional
883
+ The desired tolerance. The default is 0.0001.
884
+
885
+ Returns
886
+ -------
887
+ dictionary
888
+ A dictionary with the following keys and values:
889
+ 1. "verticalFaces": list of vertical faces
890
+ 2. "topHorizontalFaces": list of top horizontal faces
891
+ 3. "bottomHorizontalFaces": list of bottom horizontal faces
892
+ 4. "inclinedFaces": list of inclined faces
893
+ 5. "verticalApertures": list of vertical apertures
894
+ 6. "topHorizontalApertures": list of top horizontal apertures
895
+ 7. "bottomHorizontalApertures": list of bottom horizontal apertures
896
+ 8. "inclinedApertures": list of inclined apertures
897
+
898
+ """
899
+ from topologicpy.Face import Face
900
+ from topologicpy.Vector import Vector
901
+ from topologicpy.Aperture import Aperture
902
+ from topologicpy.Topology import Topology
903
+
904
+ def angleCode(f, up, tiltAngle):
905
+ dirA = Face.NormalAtParameters(f)
906
+ ang = round(Vector.Angle(dirA, up), 2)
907
+ if abs(ang - 90) < tiltAngle:
908
+ code = 0
909
+ elif abs(ang) < tiltAngle:
910
+ code = 1
911
+ elif abs(ang - 180) < tiltAngle:
912
+ code = 2
913
+ else:
914
+ code = 3
915
+ return code
916
+
917
+ def getApertures(topology):
918
+ apTopologies = []
919
+ apertures = Topology.Apertures(topology)
920
+ if isinstance(apertures, list):
921
+ for aperture in apertures:
922
+ apTopologies.append(Aperture.Topology(aperture))
923
+ return apTopologies
924
+
925
+ if not isinstance(cell, topologic.Cell):
926
+ print("Cell.Decompose - Error: The input cell parameter is not a valid topologic cell. Returning None.")
927
+ return None
928
+ verticalFaces = []
929
+ topHorizontalFaces = []
930
+ bottomHorizontalFaces = []
931
+ inclinedFaces = []
932
+ verticalApertures = []
933
+ topHorizontalApertures = []
934
+ bottomHorizontalApertures = []
935
+ inclinedApertures = []
936
+ tiltAngle = abs(tiltAngle)
937
+ faces = Cell.Faces(cell)
938
+ zList = []
939
+ for f in faces:
940
+ zList.append(f.Centroid().Z())
941
+ zMin = min(zList)
942
+ zMax = max(zList)
943
+ up = [0, 0, 1]
944
+ for aFace in faces:
945
+ aCode = angleCode(aFace, up, tiltAngle)
946
+
947
+ if aCode == 0:
948
+ verticalFaces.append(aFace)
949
+ verticalApertures += getApertures(aFace)
950
+ elif aCode == 1:
951
+ if abs(aFace.Centroid().Z() - zMin) < tolerance:
952
+ bottomHorizontalFaces.append(aFace)
953
+ bottomHorizontalApertures += getApertures(aFace)
954
+ else:
955
+ topHorizontalFaces.append(aFace)
956
+ topHorizontalApertures += getApertures(aFace)
957
+ elif aCode == 2:
958
+ if abs(aFace.Centroid().Z() - zMax) < tolerance:
959
+ topHorizontalFaces.append(aFace)
960
+ topHorizontalApertures += getApertures(aFace)
961
+ else:
962
+ bottomHorizontalFaces.append(aFace)
963
+ bottomHorizontalApertures += getApertures(aFace)
964
+ elif aCode == 3:
965
+ inclinedFaces.append(aFace)
966
+ inclinedApertures += getApertures(aFace)
967
+ d = {
968
+ "verticalFaces" : verticalFaces,
969
+ "topHorizontalFaces" : topHorizontalFaces,
970
+ "bottomHorizontalFaces" : bottomHorizontalFaces,
971
+ "inclinedFaces" : inclinedFaces,
972
+ "verticalApertures" : verticalApertures,
973
+ "topHorizontalApertures" : topHorizontalApertures,
974
+ "bottomHorizontalApertures" : bottomHorizontalApertures,
975
+ "inclinedApertures" : inclinedApertures
976
+ }
977
+ return d
978
+
979
+ @staticmethod
980
+ def Dodecahedron(origin: topologic.Vertex = None, radius: float = 0.5,
981
+ direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
982
+ """
983
+ Description
984
+ ----------
985
+ Creates a dodecahedron. See https://en.wikipedia.org/wiki/Dodecahedron.
986
+
987
+ Parameters
988
+ ----------
989
+ origin : topologic.Vertex , optional
990
+ The origin location of the dodecahedron. The default is None which results in the dodecahedron being placed at (0, 0, 0).
991
+ radius : float , optional
992
+ The radius of the dodecahedron's circumscribed sphere. The default is 0.5.
993
+ direction : list , optional
994
+ The vector representing the up direction of the dodecahedron. The default is [0, 0, 1].
995
+ placement : str , optional
996
+ The description of the placement of the origin of the dodecahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
997
+ tolerance : float , optional
998
+ The desired tolerance. The default is 0.0001.
999
+
1000
+ Returns
1001
+ -------
1002
+ topologic.Cell
1003
+ The created dodecahedron.
1004
+
1005
+ """
1006
+ from topologicpy.Vertex import Vertex
1007
+ from topologicpy.Edge import Edge
1008
+ from topologicpy.Face import Face
1009
+ from topologicpy.Cluster import Cluster
1010
+ from topologicpy.Topology import Topology
1011
+
1012
+ if not origin:
1013
+ origin = Vertex.ByCoordinates(0, 0, 0)
1014
+ if not isinstance(origin, topologic.Vertex):
1015
+ print("Cell.Dodecahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1016
+ return None
1017
+ pen = Face.Circle(sides=5, radius=0.5)
1018
+ pentagons = [pen]
1019
+ edges = Topology.Edges(pen)
1020
+ for edge in edges:
1021
+ o = Topology.Centroid(edge)
1022
+ e_dir = Edge.Direction(edge)
1023
+ pentagons.append(Topology.Rotate(pen, origin=o, axis=e_dir, angle=116.565))
1024
+
1025
+ cluster = Cluster.ByTopologies(pentagons)
1026
+
1027
+ cluster2 = Topology.Rotate(cluster, origin=Vertex.Origin(), axis=[1, 0, 0], angle=180)
1028
+ cluster2 = Topology.Rotate(cluster2, origin=Vertex.Origin(), axis=[0, 0, 1], angle=36)
1029
+ vertices = Topology.Vertices(cluster2)
1030
+ zList = [Vertex.Z(v) for v in vertices]
1031
+ zList = list(set(zList))
1032
+ zList.sort()
1033
+ zoffset1 = zList[-1] - zList[0]
1034
+ zoffset2 = zList[1] - zList[0]
1035
+ cluster2 = Topology.Translate(cluster2, 0, 0, -zoffset1-zoffset2)
1036
+ pentagons += Topology.Faces(cluster2)
1037
+ dodecahedron = Cell.ByFaces(pentagons, tolerance=tolerance)
1038
+ centroid = Topology.Centroid(dodecahedron)
1039
+ dodecahedron = Topology.Translate(dodecahedron, -Vertex.X(centroid), -Vertex.Y(centroid), -Vertex.Z(centroid))
1040
+ vertices = Topology.Vertices(dodecahedron)
1041
+ d = Vertex.Distance(Vertex.Origin(), vertices[0])
1042
+ dodecahedron = Topology.Scale(dodecahedron, origin=Vertex.Origin(), x=radius/d, y=radius/d, z=radius/d)
1043
+ if placement == "bottom":
1044
+ dodecahedron = Topology.Translate(dodecahedron, 0, 0, radius)
1045
+ elif placement == "lowerleft":
1046
+ dodecahedron = Topology.Translate(dodecahedron, radius, radius, radius)
1047
+
1048
+ dodecahedron = Topology.Orient(dodecahedron, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1049
+ dodecahedron = Topology.Place(dodecahedron, originA=Vertex.Origin(), originB=origin)
1050
+ return dodecahedron
1051
+
1052
+ @staticmethod
1053
+ def Edges(cell: topologic.Cell) -> list:
1054
+ """
1055
+ Returns the edges of the input cell.
1056
+
1057
+ Parameters
1058
+ ----------
1059
+ cell : topologic.Cell
1060
+ The input cell.
1061
+
1062
+ Returns
1063
+ -------
1064
+ list
1065
+ The list of edges.
1066
+
1067
+ """
1068
+ if not isinstance(cell, topologic.Cell):
1069
+ print("Cell.Edges - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1070
+ return None
1071
+ edges = []
1072
+ _ = cell.Edges(None, edges)
1073
+ return edges
1074
+
1075
+ @staticmethod
1076
+ def Egg(origin: topologic.Vertex = None, height: float = 1.0, uSides: int = 16, vSides: int = 8, direction: list = [0, 0, 1],
1077
+ placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1078
+ """
1079
+ Creates an egg-shaped cell.
1080
+
1081
+ Parameters
1082
+ ----------
1083
+ origin : topologic.Vertex , optional
1084
+ The origin location of the sphere. The default is None which results in the egg-shaped cell being placed at (0, 0, 0).
1085
+ height : float , optional
1086
+ The desired height of of the egg-shaped cell. The default is 1.0.
1087
+ uSides : int , optional
1088
+ The desired number of sides along the longitude of the egg-shaped cell. The default is 16.
1089
+ vSides : int , optional
1090
+ The desired number of sides along the latitude of the egg-shaped cell. The default is 8.
1091
+ direction : list , optional
1092
+ The vector representing the up direction of the egg-shaped cell. The default is [0, 0, 1].
1093
+ placement : str , optional
1094
+ The description of the placement of the origin of the egg-shaped cell. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1095
+ tolerance : float , optional
1096
+ The desired tolerance. The default is 0.0001.
1097
+
1098
+ Returns
1099
+ -------
1100
+ topologic.Cell
1101
+ The created egg-shaped cell.
1102
+
1103
+ """
1104
+
1105
+ from topologicpy.Vertex import Vertex
1106
+ from topologicpy.Topology import Topology
1107
+ from topologicpy.Dictionary import Dictionary
1108
+
1109
+ if not origin:
1110
+ origin = Vertex.ByCoordinates(0, 0, 0)
1111
+ if not isinstance(origin, topologic.Vertex):
1112
+ print("Cell.Sphere - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1113
+ return None
1114
+
1115
+ coords = [[0.0, 0.0, -0.5],
1116
+ [0.074748, 0.0, -0.494015],
1117
+ [0.140819, 0.0, -0.473222],
1118
+ [0.204118, 0.0, -0.438358],
1119
+ [0.259512, 0.0, -0.391913],
1120
+ [0.304837, 0.0, -0.335519],
1121
+ [0.338649, 0.0, -0.271416],
1122
+ [0.361307, 0.0, -0.202039],
1123
+ [0.375678, 0.0, -0.129109],
1124
+ [0.381294, 0.0, -0.053696],
1125
+ [0.377694, 0.0, 0.019874],
1126
+ [0.365135, 0.0, 0.091978],
1127
+ [0.341482, 0.0, 0.173973],
1128
+ [0.300154, 0.0, 0.276001],
1129
+ [0.252928, 0.0, 0.355989],
1130
+ [0.206605, 0.0, 0.405813],
1131
+ [0.157529, 0.0, 0.442299],
1132
+ [0.10604, 0.0, 0.472092],
1133
+ [0.05547, 0.0, 0.491784],
1134
+ [0.0, 0.0, 0.5]]
1135
+ verts = [Vertex.ByCoordinates(coord) for coord in coords]
1136
+ c = Wire.ByVertices(verts, close=False)
1137
+ new_verts = []
1138
+ for i in range(vSides+1):
1139
+ new_verts.append(Wire.VertexByParameter(c, i/vSides))
1140
+ c = Wire.ByVertices(new_verts, close=False)
1141
+ egg = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
1142
+ if egg.Type() == topologic.CellComplex.Type():
1143
+ egg = egg.ExternalBoundary()
1144
+ if egg.Type() == topologic.Shell.Type():
1145
+ egg = topologic.Cell.ByShell(egg)
1146
+ egg = Topology.Scale(egg, origin=Vertex.Origin(), x=height, y=height, z=height)
1147
+ if placement.lower() == "bottom":
1148
+ egg = Topology.Translate(egg, 0, 0, height/2)
1149
+ elif placement.lower() == "lowerleft":
1150
+ bb = Cell.BoundingBox(egg)
1151
+ d = Topology.Dictionary(bb)
1152
+ width = Dictionary.ValueAtKey(d, 'width')
1153
+ length = Dictionary.ValueAtKey(d, 'length')
1154
+ egg = Topology.Translate(egg, width*0.5, length*0.5, height*0.5)
1155
+ egg = Topology.Orient(egg, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1156
+ egg = Topology.Place(egg, originA=Vertex.Origin(), originB=origin)
1157
+ return egg
1158
+
1159
+ @staticmethod
1160
+ def ExternalBoundary(cell: topologic.Cell) -> topologic.Shell:
1161
+ """
1162
+ Returns the external boundary of the input cell.
1163
+
1164
+ Parameters
1165
+ ----------
1166
+ cell : topologic.Cell
1167
+ The input cell.
1168
+
1169
+ Returns
1170
+ -------
1171
+ topologic.Shell
1172
+ The external boundary of the input cell.
1173
+
1174
+ """
1175
+
1176
+ if not isinstance(cell, topologic.Cell):
1177
+ print("Cell.ExternalBoundary - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1178
+ return None
1179
+ try:
1180
+ return cell.ExternalBoundary()
1181
+ except:
1182
+ print("Cell.ExternalBoundary - Error: Could not compute the external boundary. Returning None.")
1183
+ return None
1184
+
1185
+ @staticmethod
1186
+ def Faces(cell: topologic.Cell) -> list:
1187
+ """
1188
+ Returns the faces of the input cell.
1189
+
1190
+ Parameters
1191
+ ----------
1192
+ cell : topologic.Cell
1193
+ The input cell.
1194
+
1195
+ Returns
1196
+ -------
1197
+ list
1198
+ The list of faces.
1199
+
1200
+ """
1201
+ if not isinstance(cell, topologic.Cell):
1202
+ print("Cell.Faces - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1203
+ return None
1204
+ faces = []
1205
+ _ = cell.Faces(None, faces)
1206
+ return faces
1207
+
1208
+ @staticmethod
1209
+ def Hyperboloid(origin: topologic.Cell = None, baseRadius: float = 0.5, topRadius: float = 0.5, height: float = 1, sides: int = 24, direction: list = [0, 0, 1],
1210
+ twist: float = 60, placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1211
+ """
1212
+ Creates a hyperboloid.
1213
+
1214
+ Parameters
1215
+ ----------
1216
+ origin : topologic.Vertex , optional
1217
+ The location of the origin of the hyperboloid. The default is None which results in the hyperboloid being placed at (0, 0, 0).
1218
+ baseRadius : float , optional
1219
+ The radius of the base circle of the hyperboloid. The default is 0.5.
1220
+ topRadius : float , optional
1221
+ The radius of the top circle of the hyperboloid. The default is 0.5.
1222
+ height : float , optional
1223
+ The height of the cone. The default is 1.
1224
+ sides : int , optional
1225
+ The number of sides of the cone. The default is 24.
1226
+ direction : list , optional
1227
+ The vector representing the up direction of the hyperboloid. The default is [0, 0, 1].
1228
+ twist : float , optional
1229
+ The angle to twist the base cylinder. The default is 60.
1230
+ placement : str , optional
1231
+ 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".
1232
+ tolerance : float , optional
1233
+ The desired tolerance. The default is 0.0001.
1234
+
1235
+ Returns
1236
+ -------
1237
+ topologic.Cell
1238
+ The created hyperboloid.
1239
+
1240
+ """
1241
+ from topologicpy.Cluster import Cluster
1242
+ from topologicpy.Vertex import Vertex
1243
+ from topologicpy.Face import Face
1244
+ from topologicpy.Topology import Topology
1245
+
1246
+ def createHyperboloid(baseVertices, topVertices, tolerance):
1247
+ baseWire = Wire.ByVertices(baseVertices, close=True)
1248
+ topWire = Wire.ByVertices(topVertices, close=True)
1249
+ baseFace = Face.ByWire(baseWire, tolerance=tolerance)
1250
+ topFace = Face.ByWire(topWire, tolerance=tolerance)
1251
+ faces = [baseFace, topFace]
1252
+ for i in range(0, len(baseVertices)-1):
1253
+ w = Wire.ByVertices([baseVertices[i], topVertices[i], topVertices[i+1]], close=True)
1254
+ f = Face.ByWire(w, tolerance=tolerance)
1255
+ faces.append(f)
1256
+ w = Wire.ByVertices([baseVertices[i+1], baseVertices[i], topVertices[i+1]], close=True)
1257
+ f = Face.ByWire(w, tolerance=tolerance)
1258
+ faces.append(f)
1259
+ w = Wire.ByVertices([baseVertices[-1], topVertices[-1], topVertices[0]], close=True)
1260
+ f = Face.ByWire(w, tolerance=tolerance)
1261
+ faces.append(f)
1262
+ w = Wire.ByVertices([baseVertices[0], baseVertices[-1], topVertices[0]], close=True)
1263
+ f = Face.ByWire(w, tolerance=tolerance)
1264
+ faces.append(f)
1265
+ returnTopology = Cell.ByFaces(faces, tolerance=tolerance)
1266
+ if returnTopology == None:
1267
+ returnTopology = Cluster.ByTopologies(faces)
1268
+ return returnTopology
1269
+
1270
+ if not origin:
1271
+ origin = Vertex.ByCoordinates(0, 0, 0)
1272
+ if not isinstance(origin, topologic.Vertex):
1273
+ print("Cell.Hyperboloid - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1274
+ return None
1275
+ w_origin = Vertex.Origin()
1276
+ baseV = []
1277
+ topV = []
1278
+ xOffset = 0
1279
+ yOffset = 0
1280
+ zOffset = 0
1281
+ if placement.lower() == "center":
1282
+ zOffset = -height*0.5
1283
+ elif placement.lower() == "lowerleft":
1284
+ xOffset = max(baseRadius, topRadius)
1285
+ yOffset = max(baseRadius, topRadius)
1286
+ baseZ = w_origin.Z() + zOffset
1287
+ topZ = w_origin.Z() + zOffset + height
1288
+ for i in range(sides):
1289
+ angle = math.radians(360/sides)*i
1290
+ if baseRadius > 0:
1291
+ baseX = math.sin(angle+math.radians(twist))*baseRadius + w_origin.X() + xOffset
1292
+ baseY = math.cos(angle+math.radians(twist))*baseRadius + w_origin.Y() + yOffset
1293
+ baseZ = w_origin.Z() + zOffset
1294
+ baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ))
1295
+ if topRadius > 0:
1296
+ topX = math.sin(angle-math.radians(twist))*topRadius + w_origin.X() + xOffset
1297
+ topY = math.cos(angle-math.radians(twist))*topRadius + w_origin.Y() + yOffset
1298
+ topV.append(Vertex.ByCoordinates(topX,topY,topZ))
1299
+
1300
+ hyperboloid = createHyperboloid(baseV, topV, tolerance)
1301
+ if hyperboloid == None:
1302
+ print("Cell.Hyperboloid - Error: Could not create a hyperboloid. Returning None.")
1303
+ return None
1304
+
1305
+ hyperboloid = Topology.Orient(hyperboloid, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1306
+ hyperboloid = Topology.Place(hyperboloid, originA=Vertex.Origin(), originB=origin)
1307
+ return hyperboloid
1308
+
1309
+ @staticmethod
1310
+ def Icosahedron(origin: topologic.Vertex = None, radius: float = 0.5,
1311
+ direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1312
+ """
1313
+ Description
1314
+ ----------
1315
+ Creates an icosahedron. See https://en.wikipedia.org/wiki/Icosahedron.
1316
+
1317
+ Parameters
1318
+ ----------
1319
+ origin : topologic.Vertex , optional
1320
+ The origin location of the icosahedron. The default is None which results in the icosahedron being placed at (0, 0, 0).
1321
+ radius : float , optional
1322
+ The radius of the icosahedron's circumscribed sphere. The default is 0.5.
1323
+ direction : list , optional
1324
+ The vector representing the up direction of the icosahedron. The default is [0, 0, 1].
1325
+ placement : str , optional
1326
+ The description of the placement of the origin of the icosahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1327
+ tolerance : float , optional
1328
+ The desired tolerance. The default is 0.0001.
1329
+
1330
+ Returns
1331
+ -------
1332
+ topologic.Cell
1333
+ The created icosahedron.
1334
+
1335
+ """
1336
+ from topologicpy.Vertex import Vertex
1337
+ from topologicpy.Wire import Wire
1338
+ from topologicpy.Face import Face
1339
+ from topologicpy.Topology import Topology
1340
+ import math
1341
+
1342
+ if not origin:
1343
+ origin = Vertex.ByCoordinates(0, 0, 0)
1344
+ if not isinstance(origin, topologic.Vertex):
1345
+ print("Cell.Dodecahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1346
+ return None
1347
+ rect1 = Wire.Rectangle(width=(1+math.sqrt(5))/2, length=1)
1348
+ rect2 = Wire.Rectangle(width=1, length=(1+math.sqrt(5))/2)
1349
+ rect2 = Topology.Rotate(rect2, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
1350
+ rect3 = Wire.Rectangle(width=1, length=(1+math.sqrt(5))/2)
1351
+ rect3 = Topology.Rotate(rect3, origin=Vertex.Origin(), axis=[0, 1, 0], angle=90)
1352
+ vertices = Topology.Vertices(rect1)
1353
+ v1, v2, v3, v4 = vertices
1354
+ vertices = Topology.Vertices(rect2)
1355
+ v5, v6, v7, v8 = vertices
1356
+ vertices = Topology.Vertices(rect3)
1357
+ v9, v10, v11, v12 = vertices
1358
+ f1 = Face.ByVertices([v1,v8,v4])
1359
+ f2 = Face.ByVertices([v1,v4,v5])
1360
+ f3 = Face.ByVertices([v3,v2,v6])
1361
+ f4 = Face.ByVertices([v2,v3,v7])
1362
+ f5 = Face.ByVertices([v10,v9,v2])
1363
+ f6 = Face.ByVertices([v10,v9,v1])
1364
+ f7 = Face.ByVertices([v12,v11,v4])
1365
+ f8 = Face.ByVertices([v12,v11,v3])
1366
+ f9 = Face.ByVertices([v8,v7,v9])
1367
+ f10 = Face.ByVertices([v8,v7,v12])
1368
+ f11 = Face.ByVertices([v5,v6,v10])
1369
+ f12 = Face.ByVertices([v5,v6,v11])
1370
+ f13 = Face.ByVertices([v8,v1,v9])
1371
+ f14 = Face.ByVertices([v9,v2,v7])
1372
+ f15 = Face.ByVertices([v7,v3,v12])
1373
+ f16 = Face.ByVertices([v8,v12,v4])
1374
+ f17 = Face.ByVertices([v1,v5,v10])
1375
+ f18 = Face.ByVertices([v10,v2,v6])
1376
+ f19 = Face.ByVertices([v6,v3,v11])
1377
+ f20 = Face.ByVertices([v11,v4,v5])
1378
+
1379
+ icosahedron = Cell.ByFaces([f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,
1380
+ f11,f12,f13,f14,f15,f16,f17,f18,f19,f20], tolerance=tolerance)
1381
+ sf = 1.051*0.5 # To insribe it in a sphere of radius 0.5
1382
+ icosahedron = Topology.Scale(icosahedron, origin=Vertex.Origin(), x=sf, y=sf, z=sf)
1383
+ sf = radius/0.5
1384
+ icosahedron = Topology.Scale(icosahedron, origin=Vertex.Origin(), x=sf, y=sf, z=sf)
1385
+ if placement == "bottom":
1386
+ icosahedron = Topology.Translate(icosahedron, 0, 0, radius)
1387
+ elif placement == "lowerleft":
1388
+ icosahedron = Topology.Translate(icosahedron, radius, radius, radius)
1389
+
1390
+ icosahedron = Topology.Orient(icosahedron, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1391
+ icosahedron = Topology.Place(icosahedron, originA=Vertex.Origin(), originB=origin)
1392
+ return icosahedron
1393
+
1394
+
1395
+ @staticmethod
1396
+ def InternalBoundaries(cell: topologic.Cell) -> list:
1397
+ """
1398
+ Returns the internal boundaries of the input cell.
1399
+
1400
+ Parameters
1401
+ ----------
1402
+ cell : topologic.Cell
1403
+ The input cell.
1404
+
1405
+ Returns
1406
+ -------
1407
+ list
1408
+ The list of internal boundaries ([topologic.Shell]).
1409
+
1410
+ """
1411
+ shells = []
1412
+ _ = cell.InternalBoundaries(shells)
1413
+ return shells
1414
+
1415
+ @staticmethod
1416
+ def InternalVertex(cell: topologic.Cell, tolerance: float = 0.0001):
1417
+ """
1418
+ Creates a vertex that is guaranteed to be inside the input cell.
1419
+
1420
+ Parameters
1421
+ ----------
1422
+ cell : topologic.Cell
1423
+ The input cell.
1424
+ tolerance : float , optional
1425
+ The desired tolerance. The default is 0.0001.
1426
+
1427
+ Returns
1428
+ -------
1429
+ topologic.Vertex
1430
+ The internal vertex.
1431
+
1432
+ """
1433
+
1434
+ if not isinstance(cell, topologic.Cell):
1435
+ print("Cell.InternalVertex - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1436
+ return None
1437
+ try:
1438
+ return topologic.CellUtility.InternalVertex(cell, tolerance)
1439
+ except:
1440
+ print("Cell.InternalVertex - Error: Could not create an internal vertex. Returning None.")
1441
+ return None
1442
+
1443
+ @staticmethod
1444
+ def IsOnBoundary(cell: topologic.Cell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
1445
+ """
1446
+ Returns True if the input vertex is on the boundary of the input cell. Returns False otherwise.
1447
+
1448
+ Parameters
1449
+ ----------
1450
+ cell : topologic.Cell
1451
+ The input cell.
1452
+ vertex : topologic.Vertex
1453
+ The input vertex.
1454
+ tolerance : float , optional
1455
+ The desired tolerance. The default is 0.0001.
1456
+
1457
+ Returns
1458
+ -------
1459
+ bool
1460
+ Returns True if the input vertex is inside the input cell. Returns False otherwise.
1461
+
1462
+ """
1463
+
1464
+ if not isinstance(cell, topologic.Cell):
1465
+ print("Cell.IsOnBoundary - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1466
+ return None
1467
+ if not isinstance(vertex, topologic.Vertex):
1468
+ print("Cell.IsOnBoundary - Error: The input vertex parameter is not a valid topologic vertex. Returning None.")
1469
+ return None
1470
+ try:
1471
+ return (topologic.CellUtility.Contains(cell, vertex, tolerance) == 1)
1472
+ except:
1473
+ print("Cell.IsOnBoundary - Error: Could not determine if the input vertex is on the boundary of the input cell. Returning None.")
1474
+ return None
1475
+
1476
+ @staticmethod
1477
+ def Octahedron(origin: topologic.Vertex = None, radius: float = 0.5,
1478
+ direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1479
+ """
1480
+ Description
1481
+ ----------
1482
+ Creates an octahedron. See https://en.wikipedia.org/wiki/Octahedron.
1483
+
1484
+ Parameters
1485
+ ----------
1486
+ origin : topologic.Vertex , optional
1487
+ The origin location of the octahedron. The default is None which results in the octahedron being placed at (0, 0, 0).
1488
+ radius : float , optional
1489
+ The radius of the octahedron's circumscribed sphere. The default is 0.5.
1490
+ direction : list , optional
1491
+ The vector representing the up direction of the octahedron. The default is [0, 0, 1].
1492
+ placement : str , optional
1493
+ The description of the placement of the origin of the octahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1494
+ tolerance : float , optional
1495
+ The desired tolerance. The default is 0.0001.
1496
+
1497
+ Returns
1498
+ -------
1499
+ topologic.Cell
1500
+ The created octahedron.
1501
+
1502
+ """
1503
+
1504
+ from topologicpy.Vertex import Vertex
1505
+ from topologicpy.Face import Face
1506
+ from topologicpy.Topology import Topology
1507
+
1508
+ if not origin:
1509
+ origin = Vertex.ByCoordinates(0, 0, 0)
1510
+ if not isinstance(origin, topologic.Vertex):
1511
+ print("Cell.Octahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1512
+ return None
1513
+
1514
+ vb1 = Vertex.ByCoordinates(-0.5, 0, 0)
1515
+ vb2 = Vertex.ByCoordinates(0, -0.5, 0)
1516
+ vb3 = Vertex.ByCoordinates(0.5, 0, 0)
1517
+ vb4 = Vertex.ByCoordinates(0, 0.5, 0)
1518
+ top = Vertex.ByCoordinates(0, 0, 0.5)
1519
+ bottom = Vertex.ByCoordinates(0, 0, -0.5)
1520
+ f1 = Face.ByVertices([top, vb1, vb2])
1521
+ f2 = Face.ByVertices([top, vb2, vb3])
1522
+ f3 = Face.ByVertices([top, vb3, vb4])
1523
+ f4 = Face.ByVertices([top, vb4, vb1])
1524
+ f5 = Face.ByVertices([bottom, vb1, vb2])
1525
+ f6 = Face.ByVertices([bottom, vb2, vb3])
1526
+ f7 = Face.ByVertices([bottom, vb3, vb4])
1527
+ f8 = Face.ByVertices([bottom, vb4, vb1])
1528
+
1529
+ octahedron = Cell.ByFaces([f1, f2, f3, f4, f5, f6, f7, f8], tolerance=tolerance)
1530
+ octahedron = Topology.Scale(octahedron, origin=Vertex.Origin(), x=radius/0.5, y=radius/0.5, z=radius/0.5)
1531
+ if placement == "bottom":
1532
+ octahedron = Topology.Translate(octahedron, 0, 0, radius)
1533
+ elif placement == "lowerleft":
1534
+ octahedron = Topology.Translate(octahedron, radius, radius, radius)
1535
+ octahedron = Topology.Orient(octahedron, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1536
+ octahedron = Topology.Place(octahedron, originA=Vertex.Origin(), originB=origin)
1537
+ return octahedron
1538
+
1539
+ @staticmethod
1540
+ 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:
1541
+ """
1542
+ Description
1543
+ ----------
1544
+ Creates a pipe along the input edge.
1545
+
1546
+ Parameters
1547
+ ----------
1548
+ edge : topologic.Edge
1549
+ The centerline of the pipe.
1550
+ profile : topologic.Wire , optional
1551
+ 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.
1552
+ radius : float , optional
1553
+ The radius of the pipe. The default is 0.5.
1554
+ sides : int , optional
1555
+ The number of sides of the pipe. The default is 16.
1556
+ startOffset : float , optional
1557
+ The offset distance from the start vertex of the centerline edge. The default is 0.
1558
+ endOffset : float , optional
1559
+ The offset distance from the end vertex of the centerline edge. The default is 0.
1560
+ endcapA : topologic.Topology, optional
1561
+ 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.
1562
+ endcapB : topologic.Topology, optional
1563
+ 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.
1564
+
1565
+ Returns
1566
+ -------
1567
+ dict
1568
+ A dictionary containing the pipe, the start endcap, and the end endcap if they have been specified. The dictionary has the following keys:
1569
+ 'pipe'
1570
+ 'endcapA'
1571
+ 'endcapB'
1572
+
1573
+ """
1574
+
1575
+ from topologicpy.Vertex import Vertex
1576
+ from topologicpy.Edge import Edge
1577
+ from topologicpy.Topology import Topology
1578
+
1579
+ if not isinstance(edge, topologic.Edge):
1580
+ print("Cell.Pipe - Error: The input edge parameter is not a valid topologic edge. Returning None.")
1581
+ return None
1582
+ length = Edge.Length(edge)
1583
+ origin = Edge.StartVertex(edge)
1584
+ startU = startOffset / length
1585
+ endU = 1.0 - (endOffset / length)
1586
+ sv = Edge.VertexByParameter(edge, startU)
1587
+ ev = Edge.VertexByParameter(edge, endU)
1588
+ x1 = sv.X()
1589
+ y1 = sv.Y()
1590
+ z1 = sv.Z()
1591
+ x2 = ev.X()
1592
+ y2 = ev.Y()
1593
+ z2 = ev.Z()
1594
+ dx = x2 - x1
1595
+ dy = y2 - y1
1596
+ dz = z2 - z1
1597
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1598
+ baseV = []
1599
+ topV = []
1600
+
1601
+ if isinstance(profile, topologic.Wire):
1602
+ baseWire = Topology.Translate(profile, 0 , 0, sv.Z())
1603
+ topWire = Topology.Translate(profile, 0 , 0, sv.Z()+dist)
1604
+ else:
1605
+ for i in range(sides):
1606
+ angle = math.radians(360/sides)*i
1607
+ x = math.sin(angle)*radius + sv.X()
1608
+ y = math.cos(angle)*radius + sv.Y()
1609
+ z = sv.Z()
1610
+ baseV.append(Vertex.ByCoordinates(x, y, z))
1611
+ topV.append(Vertex.ByCoordinates(x, y, z+dist))
1612
+
1613
+ baseWire = Wire.ByVertices(baseV)
1614
+ topWire = Wire.ByVertices(topV)
1615
+ wires = [baseWire, topWire]
1616
+ pipe = Cell.ByWires(wires)
1617
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1618
+ if dist < 0.0001:
1619
+ theta = 0
1620
+ else:
1621
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1622
+ pipe = Topology.Rotate(pipe, origin=sv, axis=[0, 1, 0], angle=theta)
1623
+ pipe = Topology.Rotate(pipe, origin=sv, axis=[0, 0, 1], angle=phi)
1624
+ zzz = Vertex.ByCoordinates(0, 0, 0)
1625
+ if endcapA:
1626
+ origin = edge.StartVertex()
1627
+ x1 = origin.X()
1628
+ y1 = origin.Y()
1629
+ z1 = origin.Z()
1630
+ x2 = edge.EndVertex().X()
1631
+ y2 = edge.EndVertex().Y()
1632
+ z2 = edge.EndVertex().Z()
1633
+ dx = x2 - x1
1634
+ dy = y2 - y1
1635
+ dz = z2 - z1
1636
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1637
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1638
+ if dist < 0.0001:
1639
+ theta = 0
1640
+ else:
1641
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1642
+ endcapA = Topology.Copy(endcapA)
1643
+ endcapA = Topology.Rotate(endcapA, origin=zzz, axis=[0, 1, 0], angle=theta)
1644
+ endcapA = Topology.Rotate(endcapA, origin=zzz, axis=[0, 0, 1], angle=phi+180)
1645
+ endcapA = Topology.Translate(endcapA, origin.X(), origin.Y(), origin.Z())
1646
+ if endcapB:
1647
+ origin = edge.EndVertex()
1648
+ x1 = origin.X()
1649
+ y1 = origin.Y()
1650
+ z1 = origin.Z()
1651
+ x2 = edge.StartVertex().X()
1652
+ y2 = edge.StartVertex().Y()
1653
+ z2 = edge.StartVertex().Z()
1654
+ dx = x2 - x1
1655
+ dy = y2 - y1
1656
+ dz = z2 - z1
1657
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1658
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1659
+ if dist < 0.0001:
1660
+ theta = 0
1661
+ else:
1662
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1663
+ endcapB = Topology.Copy(endcapB)
1664
+ endcapB = Topology.Rotate(endcapB, origin=zzz, axis=[0, 1, 0], angle=theta)
1665
+ endcapB = Topology.Rotate(endcapB, origin=zzz, axis=[0, 0, 1], angle=phi+180)
1666
+ endcapB = Topology.Translate(endcapB, origin.X(), origin.Y(), origin.Z())
1667
+ return {'pipe': pipe, 'endcapA': endcapA, 'endcapB': endcapB}
1668
+
1669
+ @staticmethod
1670
+ def Prism(origin: topologic.Vertex = None, width: float = 1, length: float = 1, height: float = 1, uSides: int = 1, vSides: int = 1, wSides: int = 1,
1671
+ direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1672
+ """
1673
+ Description
1674
+ ----------
1675
+ Creates a prism.
1676
+
1677
+ Parameters
1678
+ ----------
1679
+ origin : topologic.Vertex , optional
1680
+ The origin location of the prism. The default is None which results in the prism being placed at (0, 0, 0).
1681
+ width : float , optional
1682
+ The width of the prism. The default is 1.
1683
+ length : float , optional
1684
+ The length of the prism. The default is 1.
1685
+ height : float , optional
1686
+ The height of the prism.
1687
+ uSides : int , optional
1688
+ The number of sides along the width. The default is 1.
1689
+ vSides : int , optional
1690
+ The number of sides along the length. The default is 1.
1691
+ wSides : int , optional
1692
+ The number of sides along the height. The default is 1.
1693
+ direction : list , optional
1694
+ The vector representing the up direction of the prism. The default is [0, 0, 1].
1695
+ placement : str , optional
1696
+ 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".
1697
+ tolerance : float , optional
1698
+ The desired tolerance. The default is 0.0001.
1699
+
1700
+ Returns
1701
+ -------
1702
+ topologic.Cell
1703
+ The created prism.
1704
+
1705
+ """
1706
+ def sliceCell(cell, width, length, height, uSides, vSides, wSides):
1707
+ origin = cell.Centroid()
1708
+ shells = []
1709
+ _ = cell.Shells(None, shells)
1710
+ shell = shells[0]
1711
+ wRect = Wire.Rectangle(origin=origin, width=width*1.2, length=length*1.2, direction=[0, 0, 1], placement="center")
1712
+ sliceFaces = []
1713
+ for i in range(1, wSides):
1714
+ sliceFaces.append(Topology.Translate(Face.ByWire(wRect, tolerance=tolerance), 0, 0, height/wSides*i - height*0.5))
1715
+ uRect = Wire.Rectangle(origin=origin, width=height*1.2, length=length*1.2, direction=[1, 0, 0], placement="center")
1716
+ for i in range(1, uSides):
1717
+ sliceFaces.append(Topology.Translate(Face.ByWire(uRect, tolerance=tolerance), width/uSides*i - width*0.5, 0, 0))
1718
+ vRect = Wire.Rectangle(origin=origin, width=height*1.2, length=width*1.2, direction=[0, 1, 0], placement="center")
1719
+ for i in range(1, vSides):
1720
+ sliceFaces.append(Topology.Translate(Face.ByWire(vRect, tolerance=tolerance), 0, length/vSides*i - length*0.5, 0))
1721
+ if len(sliceFaces) > 0:
1722
+ sliceCluster = topologic.Cluster.ByTopologies(sliceFaces)
1723
+ shell = Topology.Slice(topologyA=shell, topologyB=sliceCluster, tranDict=False, tolerance=tolerance)
1724
+ return Cell.ByShell(shell)
1725
+ return cell
1726
+
1727
+ from topologicpy.Vertex import Vertex
1728
+ from topologicpy.Face import Face
1729
+ from topologicpy.Topology import Topology
1730
+
1731
+ if not origin:
1732
+ origin = Vertex.ByCoordinates(0, 0, 0)
1733
+ if not isinstance(origin, topologic.Vertex):
1734
+ print("Cell.Prism - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1735
+ return None
1736
+ xOffset = 0
1737
+ yOffset = 0
1738
+ zOffset = 0
1739
+ if placement.lower() == "center":
1740
+ zOffset = -height*0.5
1741
+ elif placement.lower() == "lowerleft":
1742
+ xOffset = width*0.5
1743
+ yOffset = length*0.5
1744
+ vb1 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset)
1745
+ vb2 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset)
1746
+ vb3 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset)
1747
+ vb4 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset)
1748
+
1749
+ baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
1750
+ baseFace = Face.ByWire(baseWire, tolerance=tolerance)
1751
+
1752
+ prism = Cell.ByThickenedFace(baseFace, thickness=height, bothSides = False)
1753
+
1754
+ if uSides > 1 or vSides > 1 or wSides > 1:
1755
+ prism = sliceCell(prism, width, length, height, uSides, vSides, wSides)
1756
+ prism = Topology.Orient(prism, origin=origin, dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1757
+ return prism
1758
+
1759
+ @staticmethod
1760
+ def RemoveCollinearEdges(cell: topologic.Cell, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
1761
+ """
1762
+ Removes any collinear edges in the input cell.
1763
+
1764
+ Parameters
1765
+ ----------
1766
+ cell : topologic.Cell
1767
+ The input cell.
1768
+ angTolerance : float , optional
1769
+ The desired angular tolerance. The default is 0.1.
1770
+ tolerance : float , optional
1771
+ The desired tolerance. The default is 0.0001.
1772
+
1773
+ Returns
1774
+ -------
1775
+ topologic.Cell
1776
+ The created cell without any collinear edges.
1777
+
1778
+ """
1779
+ from topologicpy.Face import Face
1780
+
1781
+ if not isinstance(cell, topologic.Cell):
1782
+ print("Cell.RemoveCollinearEdges - Error: The input cell parameter is not a valid cell. Returning None.")
1783
+ return None
1784
+ faces = Cell.Faces(cell)
1785
+ clean_faces = []
1786
+ for face in faces:
1787
+ clean_faces.append(Face.RemoveCollinearEdges(face, angTolerance=angTolerance, tolerance=tolerance))
1788
+ return Cell.ByFaces(clean_faces, tolerance=tolerance)
1789
+
1790
+ @staticmethod
1791
+ def Roof(face, angle: float = 45, epsilon: float = 0.01 , tolerance: float = 0.001):
1792
+ """
1793
+ Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
1794
+ This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
1795
+
1796
+ Parameters
1797
+ ----------
1798
+ face : topologic.Face
1799
+ The input face.
1800
+ angle : float , optioal
1801
+ The desired angle in degrees of the roof. The default is 45.
1802
+ epsilon : float , optional
1803
+ The desired epsilon (another form of tolerance for distance from plane). The default is 0.01. (This is set to a larger number as it was found to work better)
1804
+ tolerance : float , optional
1805
+ The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
1806
+
1807
+ Returns
1808
+ -------
1809
+ cell
1810
+ The created roof.
1811
+
1812
+ """
1813
+ from topologicpy.Shell import Shell
1814
+ from topologicpy.Cell import Cell
1815
+ from topologicpy.Topology import Topology
1816
+
1817
+ shell = Shell.Roof(face=face, angle=angle, epsilon=epsilon, tolerance=tolerance)
1818
+ faces = Topology.Faces(shell) + [face]
1819
+ cell = Cell.ByFaces(faces, tolerance=tolerance)
1820
+ if not cell:
1821
+ print("Cell.Roof - Error: Could not create a roof cell. Returning None.")
1822
+ return None
1823
+ return cell
1824
+
1825
+ @staticmethod
1826
+ def Sets(cells: list, superCells: list, tolerance: float = 0.0001) -> list:
1827
+ """
1828
+ 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.
1829
+
1830
+ Parameters
1831
+ ----------
1832
+ inputCells : list
1833
+ The list of input cells.
1834
+ superCells : list
1835
+ The list of super cells.
1836
+ tolerance : float , optional
1837
+ The desired tolerance. The default is 0.0001.
1838
+
1839
+ Returns
1840
+ -------
1841
+ list
1842
+ The classified list of input cells based on their encolsure within the input list of super cells.
1843
+
1844
+ """
1845
+
1846
+ from topologicpy.Vertex import Vertex
1847
+ from topologicpy.Topology import Topology
1848
+
1849
+ if not isinstance(cells, list):
1850
+ print("Cell.Sets - Error: The input cells parameter is not a valid list. Returning None.")
1851
+ return None
1852
+ if not isinstance(superCells, list):
1853
+ print("Cell.Sets - Error: The input superCells parameter is not a valid list. Returning None.")
1854
+ return None
1855
+ cells = [c for c in cells if isinstance(c, topologic.Cell)]
1856
+ if len(cells) < 1:
1857
+ print("Cell.Sets - Error: The input cells parameter does not contain any valid cells. Returning None.")
1858
+ return None
1859
+ superCells = [c for c in superCells if isinstance(c, topologic.Cell)]
1860
+ if len(cells) < 1:
1861
+ print("Cell.Sets - Error: The input cells parameter does not contain any valid cells. Returning None.")
1862
+ return None
1863
+ if len(superCells) == 0:
1864
+ cluster = cells[0]
1865
+ for i in range(1, len(cells)):
1866
+ oldCluster = cluster
1867
+ cluster = cluster.Union(cells[i])
1868
+ del oldCluster
1869
+ superCells = []
1870
+ _ = cluster.Cells(None, superCells)
1871
+ unused = []
1872
+ for i in range(len(cells)):
1873
+ unused.append(True)
1874
+ sets = []
1875
+ for i in range(len(superCells)):
1876
+ sets.append([])
1877
+ for i in range(len(cells)):
1878
+ if unused[i]:
1879
+ iv = Topology.InternalVertex(cells[i], tolerance=tolerance)
1880
+ for j in range(len(superCells)):
1881
+ if (Vertex.IsInternal(iv, superCells[j], tolerance)):
1882
+ sets[j].append(cells[i])
1883
+ unused[i] = False
1884
+ return sets
1885
+
1886
+ @staticmethod
1887
+ def Shells(cell: topologic.Cell) -> list:
1888
+ """
1889
+ Returns the shells of the input cell.
1890
+
1891
+ Parameters
1892
+ ----------
1893
+ cell : topologic.Cell
1894
+ The input cell.
1895
+
1896
+ Returns
1897
+ -------
1898
+ list
1899
+ The list of shells.
1900
+
1901
+ """
1902
+ if not isinstance(cell, topologic.Cell):
1903
+ print("Cell.Shells - Error: The input cell parameter is not a valid topologic cell. Returning None.")
1904
+ return None
1905
+ shells = []
1906
+ _ = cell.Shells(None, shells)
1907
+ return shells
1908
+
1909
+ @staticmethod
1910
+ def Sphere(origin: topologic.Vertex = None, radius: float = 0.5, uSides: int = 16, vSides: int = 8, direction: list = [0, 0, 1],
1911
+ placement: str = "center", tolerance: float = 0.0001) -> topologic.Cell:
1912
+ """
1913
+ Creates a sphere.
1914
+
1915
+ Parameters
1916
+ ----------
1917
+ origin : topologic.Vertex , optional
1918
+ The origin location of the sphere. The default is None which results in the sphere being placed at (0, 0, 0).
1919
+ radius : float , optional
1920
+ The radius of the sphere. The default is 0.5.
1921
+ uSides : int , optional
1922
+ The number of sides along the longitude of the sphere. The default is 16.
1923
+ vSides : int , optional
1924
+ The number of sides along the latitude of the sphere. The default is 8.
1925
+ direction : list , optional
1926
+ The vector representing the up direction of the sphere. The default is [0, 0, 1].
1927
+ placement : str , optional
1928
+ 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".
1929
+ tolerance : float , optional
1930
+ The desired tolerance. The default is 0.0001.
1931
+
1932
+ Returns
1933
+ -------
1934
+ topologic.Cell
1935
+ The created sphere.
1936
+
1937
+ """
1938
+
1939
+ from topologicpy.Vertex import Vertex
1940
+ from topologicpy.Topology import Topology
1941
+
1942
+ if not origin:
1943
+ origin = Vertex.ByCoordinates(0, 0, 0)
1944
+ if not isinstance(origin, topologic.Vertex):
1945
+ print("Cell.Sphere - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
1946
+ return None
1947
+ c = Wire.Circle(origin=Vertex.Origin(), radius=radius, sides=vSides, fromAngle=-90, toAngle=90, close=False, direction=[0, 0, 1], placement="center")
1948
+ c = Topology.Rotate(c, origin=Vertex.Origin(), axis=[1, 0, 0], angle=90)
1949
+ sphere = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
1950
+ if sphere.Type() == topologic.CellComplex.Type():
1951
+ sphere = sphere.ExternalBoundary()
1952
+ if sphere.Type() == topologic.Shell.Type():
1953
+ sphere = topologic.Cell.ByShell(sphere)
1954
+ if placement.lower() == "bottom":
1955
+ sphere = Topology.Translate(sphere, 0, 0, radius)
1956
+ elif placement.lower() == "lowerleft":
1957
+ sphere = Topology.Translate(sphere, radius, radius, radius)
1958
+ sphere = Topology.Orient(sphere, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
1959
+ sphere = Topology.Place(sphere, originA=Vertex.Origin(), originB=origin)
1960
+ return sphere
1961
+
1962
+ @staticmethod
1963
+ def SurfaceArea(cell: topologic.Cell, mantissa: int = 6) -> float:
1964
+ """
1965
+ Returns the surface area of the input cell.
1966
+
1967
+ Parameters
1968
+ ----------
1969
+ cell : topologic.Cell
1970
+ The cell.
1971
+ mantissa : int , optional
1972
+ The desired length of the mantissa. The default is 6.
1973
+
1974
+ Returns
1975
+ -------
1976
+ area : float
1977
+ The surface area of the input cell.
1978
+
1979
+ """
1980
+ return Cell.Area(cell=cell, mantissa=mantissa)
1981
+
1982
+ @staticmethod
1983
+ def Tetrahedron(origin: topologic.Vertex = None, radius: float = 0.5,
1984
+ direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Cell:
1985
+ """
1986
+ Description
1987
+ ----------
1988
+ Creates a tetrahedron. See https://en.wikipedia.org/wiki/Tetrahedron.
1989
+
1990
+ Parameters
1991
+ ----------
1992
+ origin : topologic.Vertex , optional
1993
+ The origin location of the tetrahedron. The default is None which results in the tetrahedron being placed at (0, 0, 0).
1994
+ radius : float , optional
1995
+ The radius of the tetrahedron's circumscribed sphere. The default is 0.5.
1996
+ direction : list , optional
1997
+ The vector representing the up direction of the tetrahedron. The default is [0, 0, 1].
1998
+ placement : str , optional
1999
+ The description of the placement of the origin of the tetrahedron. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
2000
+ tolerance : float , optional
2001
+ The desired tolerance. The default is 0.0001.
2002
+
2003
+ Returns
2004
+ -------
2005
+ topologic.Cell
2006
+ The created tetrahedron.
2007
+
2008
+ """
2009
+
2010
+ from topologicpy.Vertex import Vertex
2011
+ from topologicpy.Wire import Wire
2012
+ from topologicpy.Face import Face
2013
+ from topologicpy.Topology import Topology
2014
+ import math
2015
+
2016
+ if not origin:
2017
+ origin = Vertex.ByCoordinates(0, 0, 0)
2018
+ if not isinstance(origin, topologic.Vertex):
2019
+ print("Cell.Tetrahedron - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
2020
+ return None
2021
+
2022
+ vb1 = Vertex.ByCoordinates(math.sqrt(8/9), 0, -1/3)
2023
+ vb2 = Vertex.ByCoordinates(-math.sqrt(2/9), math.sqrt(2/3), -1/3)
2024
+ vb3 = Vertex.ByCoordinates(-math.sqrt(2/9), -math.sqrt(2/3), -1/3)
2025
+ vb4 = Vertex.ByCoordinates(0, 0, 1)
2026
+ f1 = Face.ByVertices([vb1, vb2, vb3])
2027
+ f2 = Face.ByVertices([vb4, vb1, vb2])
2028
+ f3 = Face.ByVertices([vb4, vb2, vb3])
2029
+ f4 = Face.ByVertices([vb4, vb3, vb1])
2030
+ tetrahedron = Cell.ByFaces([f1, f2, f3, f4])
2031
+ tetrahedron = Topology.Scale(tetrahedron, origin=Vertex.Origin(), x=0.5, y=0.5, z=0.5)
2032
+ tetrahedron = Topology.Scale(tetrahedron, origin=Vertex.Origin(), x=radius/0.5, y=radius/0.5, z=radius/0.5)
2033
+
2034
+ if placement.lower() == "lowerleft":
2035
+ tetrahedron = Topology.Translate(tetrahedron, radius, radius, radius)
2036
+ elif placement.lower() == "bottom":
2037
+ tetrahedron = Topology.Translate(tetrahedron, 0, 0, radius)
2038
+ tetrahedron = Topology.Place(tetrahedron, originA=Vertex.Origin(), originB=origin)
2039
+ tetrahedron = Topology.Orient(tetrahedron, origin=origin, dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
2040
+ return tetrahedron
2041
+
2042
+ @staticmethod
2043
+ 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:
2044
+ """
2045
+ Creates a torus.
2046
+
2047
+ Parameters
2048
+ ----------
2049
+ origin : topologic.Vertex , optional
2050
+ The origin location of the torus. The default is None which results in the torus being placed at (0, 0, 0).
2051
+ majorRadius : float , optional
2052
+ The major radius of the torus. The default is 0.5.
2053
+ minorRadius : float , optional
2054
+ The minor radius of the torus. The default is 0.1.
2055
+ uSides : int , optional
2056
+ The number of sides along the longitude of the torus. The default is 16.
2057
+ vSides : int , optional
2058
+ The number of sides along the latitude of the torus. The default is 8.
2059
+ direction : list , optional
2060
+ The vector representing the up direction of the torus. The default is [0, 0, 1].
2061
+ placement : str , optional
2062
+ 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".
2063
+ tolerance : float , optional
2064
+ The desired tolerance. The default is 0.0001.
2065
+
2066
+ Returns
2067
+ -------
2068
+ topologic.Cell
2069
+ The created torus.
2070
+
2071
+ """
2072
+
2073
+ from topologicpy.Vertex import Vertex
2074
+ from topologicpy.Topology import Topology
2075
+
2076
+ if not origin:
2077
+ origin = Vertex.ByCoordinates(0, 0, 0)
2078
+ if not isinstance(origin, topologic.Vertex):
2079
+ print("Cell.Torus - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
2080
+ return None
2081
+ c = Wire.Circle(origin=Vertex.Origin(), radius=minorRadius, sides=vSides, fromAngle=0, toAngle=360, close=False, direction=[0, 1, 0], placement="center")
2082
+ c = Topology.Translate(c, abs(majorRadius-minorRadius), 0, 0)
2083
+ torus = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
2084
+ if torus.Type() == topologic.Shell.Type():
2085
+ torus = topologic.Cell.ByShell(torus)
2086
+ if placement.lower() == "bottom":
2087
+ torus = Topology.Translate(torus, 0, 0, minorRadius)
2088
+ elif placement.lower() == "lowerleft":
2089
+ torus = Topology.Translate(torus, majorRadius, majorRadius, minorRadius)
2090
+
2091
+ torus = Topology.Orient(torus, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
2092
+ torus = Topology.Place(torus, originA=Vertex.Origin(), originB=origin)
2093
+ return torus
2094
+
2095
+ @staticmethod
2096
+ def Vertices(cell: topologic.Cell) -> list:
2097
+ """
2098
+ Returns the vertices of the input cell.
2099
+
2100
+ Parameters
2101
+ ----------
2102
+ cell : topologic.Cell
2103
+ The input cell.
2104
+
2105
+ Returns
2106
+ -------
2107
+ list
2108
+ The list of vertices.
2109
+
2110
+ """
2111
+ if not isinstance(cell, topologic.Cell):
2112
+ print("Cell.Vertices - Error: The input cell parameter is not a valid topologic cell. Returning None.")
2113
+ return None
2114
+ vertices = []
2115
+ _ = cell.Vertices(None, vertices)
2116
+ return vertices
2117
+
2118
+ @staticmethod
2119
+ def Volume(cell: topologic.Cell, mantissa: int = 6) -> float:
2120
+ """
2121
+ Returns the volume of the input cell.
2122
+
2123
+ Parameters
2124
+ ----------
2125
+ cell : topologic.Cell
2126
+ The input cell.
2127
+ manitssa: int , optional
2128
+ The desired length of the mantissa. The default is 6.
2129
+
2130
+ Returns
2131
+ -------
2132
+ float
2133
+ The volume of the input cell.
2134
+
2135
+ """
2136
+ if not isinstance(cell, topologic.Cell):
2137
+ print("Cell.Volume - Error: The input cell parameter is not a valid topologic cell. Returning None.")
2138
+ return None
2139
+ volume = None
2140
+ try:
2141
+ volume = round(topologic.CellUtility.Volume(cell), mantissa)
2142
+ except:
2143
+ print("Cell.Volume - Error: Could not compute the volume of the input cell. Returning None.")
2144
+ volume = None
2145
+ return volume
2146
+
2147
+ @staticmethod
2148
+ def Wires(cell: topologic.Cell) -> list:
2149
+ """
2150
+ Returns the wires of the input cell.
2151
+
2152
+ Parameters
2153
+ ----------
2154
+ cell : topologic.Cell
2155
+ The input cell.
2156
+
2157
+ Returns
2158
+ -------
2159
+ list
2160
+ The list of wires.
2161
+
2162
+ """
2163
+ if not isinstance(cell, topologic.Cell):
2164
+ print("Cell.Wires - Error: The input cell parameter is not a valid topologic cell. Returning None.")
2165
+ return None
2166
+ wires = []
2167
+ _ = cell.Wires(None, wires)
2168
+ return wires
2169
+