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