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/Shell.py
ADDED
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
#from types import NoneType
|
|
2
|
+
import topologicpy
|
|
3
|
+
import topologic
|
|
4
|
+
from topologicpy.Topology import Topology
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
class Shell(Topology):
|
|
8
|
+
@staticmethod
|
|
9
|
+
def ByFaces(faces: list, tolerance: float = 0.0001) -> topologic.Shell:
|
|
10
|
+
"""
|
|
11
|
+
Creates a shell from the input list of faces.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
faces : list
|
|
16
|
+
The input list of faces.
|
|
17
|
+
tolerance : float , optional
|
|
18
|
+
The desired tolerance. The default is 0.0001.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
topologic.Shell
|
|
23
|
+
The created Shell.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
if not isinstance(faces, list):
|
|
27
|
+
return None
|
|
28
|
+
faceList = [x for x in faces if isinstance(x, topologic.Face)]
|
|
29
|
+
if len(faceList) < 1:
|
|
30
|
+
return None
|
|
31
|
+
shell = topologic.Shell.ByFaces(faceList, tolerance)
|
|
32
|
+
if not shell:
|
|
33
|
+
result = faceList[0]
|
|
34
|
+
remainder = faceList[1:]
|
|
35
|
+
cluster = topologic.Cluster.ByTopologies(remainder, False)
|
|
36
|
+
result = result.Merge(cluster, False)
|
|
37
|
+
if result.Type() > 16:
|
|
38
|
+
returnShells = []
|
|
39
|
+
_ = result.Shells(None, returnShells)
|
|
40
|
+
return returnShells
|
|
41
|
+
else:
|
|
42
|
+
return None
|
|
43
|
+
else:
|
|
44
|
+
return shell
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def ByFacesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Shell:
|
|
48
|
+
"""
|
|
49
|
+
Creates a shell from the input cluster of faces.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
cluster : topologic.Cluster
|
|
54
|
+
The input cluster of faces.
|
|
55
|
+
tolerance : float , optional
|
|
56
|
+
The desired tolerance. The default is 0.0001.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
topologic.Shell
|
|
61
|
+
The created shell.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
65
|
+
return None
|
|
66
|
+
faces = []
|
|
67
|
+
_ = cluster.Faces(None, faces)
|
|
68
|
+
return Shell.ByFaces(faces, tolerance=tolerance)
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def ByWires(wires: list, triangulate: bool = True, tolerance: float = 0.0001) -> topologic.Shell:
|
|
72
|
+
"""
|
|
73
|
+
Creates a shell by lofting through the input wires
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
wires : list
|
|
77
|
+
The input list of wires.
|
|
78
|
+
triangulate : bool , optional
|
|
79
|
+
If set to True, the faces will be triangulated. The default is True.
|
|
80
|
+
tolerance : float , optional
|
|
81
|
+
The desired tolerance. The default is 0.0001.
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
topologic.Shell
|
|
85
|
+
The creates shell.
|
|
86
|
+
"""
|
|
87
|
+
from topologicpy.Edge import Edge
|
|
88
|
+
from topologicpy.Wire import Wire
|
|
89
|
+
from topologicpy.Face import Face
|
|
90
|
+
if not isinstance(wires, list):
|
|
91
|
+
return None
|
|
92
|
+
wireList = [x for x in wires if isinstance(x, topologic.Wire)]
|
|
93
|
+
faces = []
|
|
94
|
+
for i in range(len(wireList)-1):
|
|
95
|
+
wire1 = wireList[i]
|
|
96
|
+
wire2 = wireList[i+1]
|
|
97
|
+
if wire1.Type() < topologic.Edge.Type() or wire2.Type() < topologic.Edge.Type():
|
|
98
|
+
return None
|
|
99
|
+
if wire1.Type() == topologic.Edge.Type():
|
|
100
|
+
w1_edges = [wire1]
|
|
101
|
+
else:
|
|
102
|
+
w1_edges = []
|
|
103
|
+
_ = wire1.Edges(None, w1_edges)
|
|
104
|
+
if wire2.Type() == topologic.Edge.Type():
|
|
105
|
+
w2_edges = [wire2]
|
|
106
|
+
else:
|
|
107
|
+
w2_edges = []
|
|
108
|
+
_ = wire2.Edges(None, w2_edges)
|
|
109
|
+
if len(w1_edges) != len(w2_edges):
|
|
110
|
+
return None
|
|
111
|
+
if triangulate == True:
|
|
112
|
+
for j in range (len(w1_edges)):
|
|
113
|
+
e1 = w1_edges[j]
|
|
114
|
+
e2 = w2_edges[j]
|
|
115
|
+
e3 = None
|
|
116
|
+
e4 = None
|
|
117
|
+
try:
|
|
118
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
|
|
119
|
+
except:
|
|
120
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
|
|
121
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e4])))
|
|
122
|
+
try:
|
|
123
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
|
|
124
|
+
except:
|
|
125
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
|
|
126
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e3])))
|
|
127
|
+
if e3 and e4:
|
|
128
|
+
e5 = Edge.ByVertices([e1.StartVertex(), e2.EndVertex()])
|
|
129
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e5, e4])))
|
|
130
|
+
faces.append(Face.ByWire(Wire.ByEdges([e2, e5, e3])))
|
|
131
|
+
else:
|
|
132
|
+
for j in range (len(w1_edges)):
|
|
133
|
+
e1 = w1_edges[j]
|
|
134
|
+
e2 = w2_edges[j]
|
|
135
|
+
e3 = None
|
|
136
|
+
e4 = None
|
|
137
|
+
try:
|
|
138
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
|
|
139
|
+
except:
|
|
140
|
+
try:
|
|
141
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
|
|
142
|
+
except:
|
|
143
|
+
pass
|
|
144
|
+
try:
|
|
145
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()])
|
|
146
|
+
except:
|
|
147
|
+
try:
|
|
148
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()])
|
|
149
|
+
except:
|
|
150
|
+
pass
|
|
151
|
+
if e3 and e4:
|
|
152
|
+
try:
|
|
153
|
+
faces.append(Face.ByWire(topologic.Wire.ByEdges([e1, e4, e2, e3])))
|
|
154
|
+
except:
|
|
155
|
+
faces.append(Face.ByWire(topologic.Wire.ByEdges([e1, e3, e2, e4])))
|
|
156
|
+
elif e3:
|
|
157
|
+
faces.append(Face.ByWire(topologic.Wire.ByEdges([e1, e3, e2])))
|
|
158
|
+
elif e4:
|
|
159
|
+
faces.append(Face.ByWire(topologic.Wire.ByEdges([e1, e4, e2])))
|
|
160
|
+
return Shell.ByFaces(faces, tolerance)
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def ByWiresCluster(cluster: topologic.Cluster, triangulate: bool = True, tolerance: float = 0.0001) -> topologic.Shell:
|
|
164
|
+
"""
|
|
165
|
+
Creates a shell by lofting through the input cluster of wires
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
wires : topologic.Cluster
|
|
170
|
+
The input cluster of wires.
|
|
171
|
+
triangulate : bool , optional
|
|
172
|
+
If set to True, the faces will be triangulated. The default is True.
|
|
173
|
+
tolerance : float , optional
|
|
174
|
+
The desired tolerance. The default is 0.0001.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
topologic.Shell
|
|
179
|
+
The creates shell.
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
from topologicpy.Cluster import Cluster
|
|
183
|
+
if not cluster:
|
|
184
|
+
return None
|
|
185
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
186
|
+
return None
|
|
187
|
+
wires = Cluster.Wires(cluster)
|
|
188
|
+
return Shell.ByWires(wires, triangulate=triangulate, tolerance=tolerance)
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 32, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
|
192
|
+
"""
|
|
193
|
+
Creates a circle.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
origin : topologic.Vertex , optional
|
|
198
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
|
|
199
|
+
radius : float , optional
|
|
200
|
+
The radius of the circle. The default is 0.5.
|
|
201
|
+
sides : int , optional
|
|
202
|
+
The number of sides of the circle. The default is 32.
|
|
203
|
+
fromAngle : float , optional
|
|
204
|
+
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
|
205
|
+
toAngle : float , optional
|
|
206
|
+
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
|
207
|
+
direction : list , optional
|
|
208
|
+
The vector representing the up direction of the circle. The default is [0,0,1].
|
|
209
|
+
placement : str , optional
|
|
210
|
+
The description of the placement of the origin of the pie. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
211
|
+
tolerance : float , optional
|
|
212
|
+
The desired tolerance. The default is 0.0001.
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
topologic.Shell
|
|
217
|
+
The created circle.
|
|
218
|
+
"""
|
|
219
|
+
return Shell.Pie(origin=origin, radiusA=radius, radiusB=0, sides=sides, rings=1, fromAngle=fromAngle, toAngle=toAngle, direction=direction, placement=placement, tolerance=tolerance)
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def Delaunay(vertices: list, face: topologic.Face = None) -> topologic.Shell:
|
|
223
|
+
"""
|
|
224
|
+
Returns a delaunay partitioning of the input vertices. The vertices must be coplanar. See https://en.wikipedia.org/wiki/Delaunay_triangulation.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
vertices : list
|
|
229
|
+
The input list of vertices.
|
|
230
|
+
face : topologic.Face , optional
|
|
231
|
+
The input face. If specified, the delaunay triangulation is clipped to the face.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
shell
|
|
236
|
+
A shell representing the delaunay triangulation of the input vertices.
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
from topologicpy.Vertex import Vertex
|
|
240
|
+
from topologicpy.Edge import Edge
|
|
241
|
+
from topologicpy.Wire import Wire
|
|
242
|
+
from topologicpy.Face import Face
|
|
243
|
+
from topologicpy.Cluster import Cluster
|
|
244
|
+
from topologicpy.Topology import Topology
|
|
245
|
+
from topologicpy.Dictionary import Dictionary
|
|
246
|
+
from random import sample
|
|
247
|
+
import sys
|
|
248
|
+
import subprocess
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
from scipy.spatial import Delaunay
|
|
252
|
+
except:
|
|
253
|
+
call = [sys.executable, '-m', 'pip', 'install', 'scipy', '-t', sys.path[0]]
|
|
254
|
+
subprocess.run(call)
|
|
255
|
+
try:
|
|
256
|
+
from scipy.spatial import Delaunay
|
|
257
|
+
except:
|
|
258
|
+
print("Shell.Delaunay - ERROR: Could not import scipy. Returning None.")
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
if not isinstance(vertices, list):
|
|
262
|
+
return None
|
|
263
|
+
vertices = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
|
264
|
+
if len(vertices) < 2:
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
if not isinstance(face, topologic.Face):
|
|
268
|
+
face_vertices = sample(vertices,3)
|
|
269
|
+
tempFace = Face.ByWire(Wire.ByVertices(face_vertices))
|
|
270
|
+
# Flatten the input face
|
|
271
|
+
flatFace = Face.Flatten(tempFace)
|
|
272
|
+
else:
|
|
273
|
+
flatFace = Face.Flatten(face)
|
|
274
|
+
faceVertices = Face.Vertices(face)
|
|
275
|
+
vertices += faceVertices
|
|
276
|
+
# Retrieve the needed transformations
|
|
277
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
278
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
279
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
280
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
281
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
282
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
283
|
+
|
|
284
|
+
# Create a Vertex at the world's origin (0,0,0)
|
|
285
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
286
|
+
|
|
287
|
+
# Create a cluster of the input vertices
|
|
288
|
+
verticesCluster = Cluster.ByTopologies(vertices)
|
|
289
|
+
|
|
290
|
+
# Flatten the cluster using the same transformations
|
|
291
|
+
verticesCluster = Topology.Translate(verticesCluster, -xTran, -yTran, -zTran)
|
|
292
|
+
verticesCluster = Topology.Rotate(verticesCluster, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
293
|
+
verticesCluster = Topology.Rotate(verticesCluster, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
294
|
+
|
|
295
|
+
flatVertices = Cluster.Vertices(verticesCluster)
|
|
296
|
+
tempFlatVertices = []
|
|
297
|
+
points = []
|
|
298
|
+
for flatVertex in flatVertices:
|
|
299
|
+
tempFlatVertices.append(Vertex.ByCoordinates(flatVertex.X(), flatVertex.Y(), 0))
|
|
300
|
+
points.append([flatVertex.X(), flatVertex.Y()])
|
|
301
|
+
flatVertices = tempFlatVertices
|
|
302
|
+
delaunay = Delaunay(points)
|
|
303
|
+
simplices = delaunay.simplices
|
|
304
|
+
|
|
305
|
+
faces = []
|
|
306
|
+
for simplex in simplices:
|
|
307
|
+
tempTriangleVertices = []
|
|
308
|
+
tempTriangleVertices.append(flatVertices[simplex[0]])
|
|
309
|
+
tempTriangleVertices.append(flatVertices[simplex[1]])
|
|
310
|
+
tempTriangleVertices.append(flatVertices[simplex[2]])
|
|
311
|
+
faces.append(Face.ByWire(Wire.ByVertices(tempTriangleVertices)))
|
|
312
|
+
|
|
313
|
+
shell = Shell.ByFaces(faces)
|
|
314
|
+
if isinstance(face, topologic.Face):
|
|
315
|
+
edges = Shell.Edges(shell)
|
|
316
|
+
edgesCluster = Cluster.ByTopologies(edges)
|
|
317
|
+
shell = Topology.Boolean(flatFace,edgesCluster, operation="slice")
|
|
318
|
+
shell = Topology.Rotate(shell, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
319
|
+
shell = Topology.Rotate(shell, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
320
|
+
shell = Topology.Translate(shell, xTran, yTran, zTran)
|
|
321
|
+
return shell
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def Edges(shell: topologic.Shell) -> list:
|
|
325
|
+
"""
|
|
326
|
+
Returns the edges of the input shell.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
shell : topologic.Shell
|
|
331
|
+
The input shell.
|
|
332
|
+
|
|
333
|
+
Returns
|
|
334
|
+
-------
|
|
335
|
+
list
|
|
336
|
+
The list of edges.
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
if not isinstance(shell, topologic.Shell):
|
|
340
|
+
return None
|
|
341
|
+
edges = []
|
|
342
|
+
_ = shell.Edges(None, edges)
|
|
343
|
+
return edges
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def ExternalBoundary(shell: topologic.Shell) -> topologic.Wire:
|
|
347
|
+
"""
|
|
348
|
+
Returns the external boundary (closed wire) of the input shell.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
shell : topologic.Shell
|
|
353
|
+
The input shell.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
topologic.Wire
|
|
358
|
+
The external boundary (closed wire) of the input shell.
|
|
359
|
+
|
|
360
|
+
"""
|
|
361
|
+
if not isinstance(shell, topologic.Shell):
|
|
362
|
+
return None
|
|
363
|
+
edges = []
|
|
364
|
+
_ = shell.Edges(None, edges)
|
|
365
|
+
obEdges = []
|
|
366
|
+
for anEdge in edges:
|
|
367
|
+
faces = []
|
|
368
|
+
_ = anEdge.Faces(shell, faces)
|
|
369
|
+
if len(faces) == 1:
|
|
370
|
+
obEdges.append(anEdge)
|
|
371
|
+
returnTopology = None
|
|
372
|
+
try:
|
|
373
|
+
returnTopology = topologic.Wire.ByEdges(obEdges)
|
|
374
|
+
except:
|
|
375
|
+
returnTopology = topologic.Cluster.ByTopologies(obEdges)
|
|
376
|
+
returnTopology = returnTopology.SelfMerge()
|
|
377
|
+
return returnTopology
|
|
378
|
+
|
|
379
|
+
@staticmethod
|
|
380
|
+
def Faces(shell: topologic.Shell) -> list:
|
|
381
|
+
"""
|
|
382
|
+
Returns the faces of the input shell.
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
shell : topologic.Shell
|
|
387
|
+
The input shell.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
list
|
|
392
|
+
The list of faces.
|
|
393
|
+
|
|
394
|
+
"""
|
|
395
|
+
if not isinstance(shell, topologic.Shell):
|
|
396
|
+
return None
|
|
397
|
+
faces = []
|
|
398
|
+
_ = shell.Faces(None, faces)
|
|
399
|
+
return faces
|
|
400
|
+
|
|
401
|
+
@staticmethod
|
|
402
|
+
def IsInside(shell: topologic.Shell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
|
403
|
+
"""
|
|
404
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise. Inside is defined as being inside one of the shell's faces
|
|
405
|
+
|
|
406
|
+
Parameters
|
|
407
|
+
----------
|
|
408
|
+
shell : topologic.Shell
|
|
409
|
+
The input shell.
|
|
410
|
+
vertex : topologic.Vertex
|
|
411
|
+
The input vertex.
|
|
412
|
+
tolerance : float , optional
|
|
413
|
+
The desired tolerance. The default is 0.0001.
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
bool
|
|
418
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
|
419
|
+
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
from topologicpy.Face import Face
|
|
423
|
+
if not isinstance(shell, topologic.Shell):
|
|
424
|
+
return None
|
|
425
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
426
|
+
return None
|
|
427
|
+
faces = Shell.Faces(shell)
|
|
428
|
+
for f in faces:
|
|
429
|
+
if Face.IsInside(fface=f, vertex=vertex, tolerance=tolerance):
|
|
430
|
+
return True
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
@staticmethod
|
|
434
|
+
def IsOnBoundary(shell: topologic.Shell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
|
435
|
+
"""
|
|
436
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise. Inside is defined as being inside one of the shell's faces
|
|
437
|
+
|
|
438
|
+
Parameters
|
|
439
|
+
----------
|
|
440
|
+
shell : topologic.Shell
|
|
441
|
+
The input shell.
|
|
442
|
+
vertex : topologic.Vertex
|
|
443
|
+
The input vertex.
|
|
444
|
+
tolerance : float , optional
|
|
445
|
+
The desired tolerance. The default is 0.0001.
|
|
446
|
+
|
|
447
|
+
Returns
|
|
448
|
+
-------
|
|
449
|
+
bool
|
|
450
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
|
451
|
+
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
from topologicpy.Wire import Wire
|
|
455
|
+
|
|
456
|
+
if not isinstance(shell, topologic.Shell):
|
|
457
|
+
return None
|
|
458
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
459
|
+
return None
|
|
460
|
+
boundary = Shell.ExternalBoundary(shell)
|
|
461
|
+
return Wire.IsInside(wire=boundary, vertex=vertex, tolerance=tolerance)
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def IsOutside(shell: topologic.Shell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
|
465
|
+
"""
|
|
466
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise. Inside is defined as being inside one of the shell's faces
|
|
467
|
+
|
|
468
|
+
Parameters
|
|
469
|
+
----------
|
|
470
|
+
shell : topologic.Shell
|
|
471
|
+
The input shell.
|
|
472
|
+
vertex : topologic.Vertex
|
|
473
|
+
The input vertex.
|
|
474
|
+
tolerance : float , optional
|
|
475
|
+
The desired tolerance. The default is 0.0001.
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
bool
|
|
480
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
|
481
|
+
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
if not isinstance(shell, topologic.Shell):
|
|
485
|
+
return None
|
|
486
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
487
|
+
return None
|
|
488
|
+
return not Wire.IsInside(shell=shell, vertex=vertex, tolerance=tolerance)
|
|
489
|
+
|
|
490
|
+
@staticmethod
|
|
491
|
+
def HyperbolicParaboloidRectangularDomain(origin: topologic.Vertex = None, llVertex: topologic.Vertex = None, lrVertex: topologic.Vertex =None, ulVertex: topologic.Vertex =None, urVertex: topologic.Vertex = None,
|
|
492
|
+
uSides: int = 10, vSides: int = 10, direction: list = [0,0,1], placement: str = "bottom") -> topologic.Shell:
|
|
493
|
+
"""
|
|
494
|
+
Creates a hyperbolic paraboloid with a rectangular domain.
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
origin : topologic.Vertex , optional
|
|
499
|
+
The origin of the hyperbolic parabolid. If set to None, it will be placed at the (0,0,0) origin. The default is None.
|
|
500
|
+
llVertex : topologic.Vertex , optional
|
|
501
|
+
The lower left corner of the hyperbolic parabolid. If set to None, it will be set to (-0.5,-0.5,-0.5).
|
|
502
|
+
lrVertex : topologic.Vertex , optional
|
|
503
|
+
The lower right corner of the hyperbolic parabolid. If set to None, it will be set to (0.5,-0.5,0.5).
|
|
504
|
+
ulVertex : topologic.Vertex , optional
|
|
505
|
+
The upper left corner of the hyperbolic parabolid. If set to None, it will be set to (-0.5,0.5,0.5).
|
|
506
|
+
urVertex : topologic.Vertex , optional
|
|
507
|
+
The upper right corner of the hyperbolic parabolid. If set to None, it will be set to (0.5,0.5,-0.5).
|
|
508
|
+
uSides : int , optional
|
|
509
|
+
The number of segments along the X axis. The default is 10.
|
|
510
|
+
vSides : int , optional
|
|
511
|
+
The number of segments along the Y axis. The default is 10.
|
|
512
|
+
direction : list , optional
|
|
513
|
+
The vector representing the up direction of the hyperbolic parabolid. The default is [0,0,1].
|
|
514
|
+
placement : str , optional
|
|
515
|
+
The description of the placement of the origin of the hyperbolic parabolid. This can be "center", "lowerleft", "bottom". It is case insensitive. The default is "center".
|
|
516
|
+
|
|
517
|
+
Returns
|
|
518
|
+
-------
|
|
519
|
+
topologic.Shell
|
|
520
|
+
The created hyperbolic paraboloid.
|
|
521
|
+
|
|
522
|
+
"""
|
|
523
|
+
from topologicpy.Vertex import Vertex
|
|
524
|
+
from topologicpy.Edge import Edge
|
|
525
|
+
from topologicpy.Face import Face
|
|
526
|
+
from topologicpy.Topology import Topology
|
|
527
|
+
if not isinstance(origin, topologic.Vertex):
|
|
528
|
+
origin = Vertex.ByCoordinates(0,0,0)
|
|
529
|
+
if not isinstance(llVertex, topologic.Vertex):
|
|
530
|
+
llVertex = Vertex.ByCoordinates(-0.5,-0.5,-0.5)
|
|
531
|
+
if not isinstance(lrVertex, topologic.Vertex):
|
|
532
|
+
lrVertex = Vertex.ByCoordinates(0.5,-0.5,0.5)
|
|
533
|
+
if not isinstance(ulVertex, topologic.Vertex):
|
|
534
|
+
ulVertex = Vertex.ByCoordinates(-0.5,0.5,0.5)
|
|
535
|
+
if not isinstance(urVertex, topologic.Vertex):
|
|
536
|
+
urVertex = Vertex.ByCoordinates(0.5,0.5,-0.5)
|
|
537
|
+
e1 = Edge.ByVertices([llVertex, lrVertex])
|
|
538
|
+
e3 = Edge.ByVertices([urVertex, ulVertex])
|
|
539
|
+
edges = []
|
|
540
|
+
for i in range(uSides+1):
|
|
541
|
+
v1 = Edge.VertexByParameter(e1, float(i)/float(uSides))
|
|
542
|
+
v2 = Edge.VertexByParameter(e3, 1.0 - float(i)/float(uSides))
|
|
543
|
+
edges.append(Edge.ByVertices([v1, v2]))
|
|
544
|
+
faces = []
|
|
545
|
+
for i in range(uSides):
|
|
546
|
+
for j in range(vSides):
|
|
547
|
+
v1 = Edge.VertexByParameter(edges[i], float(j)/float(vSides))
|
|
548
|
+
v2 = Edge.VertexByParameter(edges[i], float(j+1)/float(vSides))
|
|
549
|
+
v3 = Edge.VertexByParameter(edges[i+1], float(j+1)/float(vSides))
|
|
550
|
+
v4 = Edge.VertexByParameter(edges[i+1], float(j)/float(vSides))
|
|
551
|
+
faces.append(Face.ByVertices([v1, v2, v4]))
|
|
552
|
+
faces.append(Face.ByVertices([v4, v2, v3]))
|
|
553
|
+
returnTopology = Shell.ByFaces(faces)
|
|
554
|
+
if not returnTopology:
|
|
555
|
+
returnTopology = None
|
|
556
|
+
zeroOrigin = returnTopology.CenterOfMass()
|
|
557
|
+
xOffset = 0
|
|
558
|
+
yOffset = 0
|
|
559
|
+
zOffset = 0
|
|
560
|
+
minX = min([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()])
|
|
561
|
+
maxX = max([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()])
|
|
562
|
+
minY = min([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()])
|
|
563
|
+
maxY = max([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()])
|
|
564
|
+
minZ = min([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()])
|
|
565
|
+
maxZ = max([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()])
|
|
566
|
+
if placement.lower() == "lowerleft":
|
|
567
|
+
xOffset = -minX
|
|
568
|
+
yOffset = -minY
|
|
569
|
+
zOffset = -minZ
|
|
570
|
+
elif placement.lower() == "bottom":
|
|
571
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
|
572
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
|
573
|
+
zOffset = -minZ
|
|
574
|
+
elif placement.lower() == "center":
|
|
575
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
|
576
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
|
577
|
+
zOffset = -(minZ + (maxZ - minZ)*0.5)
|
|
578
|
+
x1 = 0
|
|
579
|
+
y1 = 0
|
|
580
|
+
z1 = 0
|
|
581
|
+
x2 = 0 + direction[0]
|
|
582
|
+
y2 = 0 + direction[1]
|
|
583
|
+
z2 = 0 + direction[2]
|
|
584
|
+
dx = x2 - x1
|
|
585
|
+
dy = y2 - y1
|
|
586
|
+
dz = z2 - z1
|
|
587
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
588
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
589
|
+
if dist < 0.0001:
|
|
590
|
+
theta = 0
|
|
591
|
+
else:
|
|
592
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
593
|
+
returnTopology = Topology.Rotate(returnTopology, zeroOrigin, 0, 1, 0, theta)
|
|
594
|
+
returnTopology = Topology.Rotate(returnTopology, zeroOrigin, 0, 0, 1, phi)
|
|
595
|
+
returnTopology = Topology.Translate(returnTopology, zeroOrigin.X()+xOffset, zeroOrigin.Y()+yOffset, zeroOrigin.Z()+zOffset)
|
|
596
|
+
return returnTopology
|
|
597
|
+
|
|
598
|
+
@staticmethod
|
|
599
|
+
def HyperbolicParaboloidCircularDomain(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 36, rings: int = 10, A: float = 1.0, B: float = -1.0, direction: list = [0,0,1], placement: str = "bottom") -> topologic.Shell:
|
|
600
|
+
"""
|
|
601
|
+
Creates a hyperbolic paraboloid with a circular domain. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
|
602
|
+
|
|
603
|
+
Parameters
|
|
604
|
+
----------
|
|
605
|
+
origin : topologic.Vertex , optional
|
|
606
|
+
The origin of the hyperbolic parabolid. If set to None, it will be placed at the (0,0,0) origin. The default is None.
|
|
607
|
+
radius : float , optional
|
|
608
|
+
The desired radius of the hyperbolic paraboloid. The default is 0.5.
|
|
609
|
+
sides : int , optional
|
|
610
|
+
The desired number of sides of the hyperbolic parabolid. The default is 36.
|
|
611
|
+
rings : int , optional
|
|
612
|
+
The desired number of concentric rings of the hyperbolic parabolid. The default is 10.
|
|
613
|
+
A : float , optional
|
|
614
|
+
The *A* constant in the equation z = A*x^2^ + B*y^2^. The default is 1.0.
|
|
615
|
+
B : float , optional
|
|
616
|
+
The *B* constant in the equation z = A*x^2^ + B*y^2^. The default is -1.0.
|
|
617
|
+
direction : list , optional
|
|
618
|
+
The vector representing the up direction of the hyperbolic paraboloid. The default is [0,0,1.
|
|
619
|
+
placement : str , optional
|
|
620
|
+
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "bottom". It is case insensitive. The default is "center".
|
|
621
|
+
|
|
622
|
+
Returns
|
|
623
|
+
-------
|
|
624
|
+
topologic.Shell
|
|
625
|
+
The created hyperboloic paraboloid.
|
|
626
|
+
|
|
627
|
+
"""
|
|
628
|
+
from topologicpy.Vertex import Vertex
|
|
629
|
+
from topologicpy.Face import Face
|
|
630
|
+
if not isinstance(origin, topologic.Vertex):
|
|
631
|
+
origin = Vertex.ByCoordinates(0,0,0)
|
|
632
|
+
uOffset = float(360)/float(sides)
|
|
633
|
+
vOffset = float(radius)/float(rings)
|
|
634
|
+
faces = []
|
|
635
|
+
for i in range(rings-1):
|
|
636
|
+
r1 = radius - vOffset*i
|
|
637
|
+
r2 = radius - vOffset*(i+1)
|
|
638
|
+
for j in range(sides-1):
|
|
639
|
+
a1 = math.radians(uOffset)*j
|
|
640
|
+
a2 = math.radians(uOffset)*(j+1)
|
|
641
|
+
x1 = math.sin(a1)*r1
|
|
642
|
+
y1 = math.cos(a1)*r1
|
|
643
|
+
z1 = A*x1*x1 + B*y1*y1
|
|
644
|
+
x2 = math.sin(a1)*r2
|
|
645
|
+
y2 = math.cos(a1)*r2
|
|
646
|
+
z2 = A*x2*x2 + B*y2*y2
|
|
647
|
+
x3 = math.sin(a2)*r2
|
|
648
|
+
y3 = math.cos(a2)*r2
|
|
649
|
+
z3 = A*x3*x3 + B*y3*y3
|
|
650
|
+
x4 = math.sin(a2)*r1
|
|
651
|
+
y4 = math.cos(a2)*r1
|
|
652
|
+
z4 = A*x4*x4 + B*y4*y4
|
|
653
|
+
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
|
654
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
|
655
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
|
656
|
+
v4 = topologic.Vertex.ByCoordinates(x4,y4,z4)
|
|
657
|
+
f1 = Face.ByVertices([v1,v2,v4])
|
|
658
|
+
f2 = Face.ByVertices([v4,v2,v3])
|
|
659
|
+
faces.append(f1)
|
|
660
|
+
faces.append(f2)
|
|
661
|
+
a1 = math.radians(uOffset)*(sides-1)
|
|
662
|
+
a2 = math.radians(360)
|
|
663
|
+
x1 = math.sin(a1)*r1
|
|
664
|
+
y1 = math.cos(a1)*r1
|
|
665
|
+
z1 = A*x1*x1 + B*y1*y1
|
|
666
|
+
x2 = math.sin(a1)*r2
|
|
667
|
+
y2 = math.cos(a1)*r2
|
|
668
|
+
z2 = A*x2*x2 + B*y2*y2
|
|
669
|
+
x3 = math.sin(a2)*r2
|
|
670
|
+
y3 = math.cos(a2)*r2
|
|
671
|
+
z3 = A*x3*x3 + B*y3*y3
|
|
672
|
+
x4 = math.sin(a2)*r1
|
|
673
|
+
y4 = math.cos(a2)*r1
|
|
674
|
+
z4 = A*x4*x4 + B*y4*y4
|
|
675
|
+
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
|
676
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
|
677
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
|
678
|
+
v4 = topologic.Vertex.ByCoordinates(x4,y4,z4)
|
|
679
|
+
f1 = Face.ByVertices([v1,v2,v4])
|
|
680
|
+
f2 = Face.ByVertices([v4,v2,v3])
|
|
681
|
+
faces.append(f1)
|
|
682
|
+
faces.append(f2)
|
|
683
|
+
# Special Case: Center triangles
|
|
684
|
+
r = vOffset
|
|
685
|
+
x1 = 0
|
|
686
|
+
y1 = 0
|
|
687
|
+
z1 = 0
|
|
688
|
+
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
|
689
|
+
for j in range(sides-1):
|
|
690
|
+
a1 = math.radians(uOffset)*j
|
|
691
|
+
a2 = math.radians(uOffset)*(j+1)
|
|
692
|
+
x2 = math.sin(a1)*r
|
|
693
|
+
y2 = math.cos(a1)*r
|
|
694
|
+
z2 = A*x2*x2 + B*y2*y2
|
|
695
|
+
#z2 = 0
|
|
696
|
+
x3 = math.sin(a2)*r
|
|
697
|
+
y3 = math.cos(a2)*r
|
|
698
|
+
z3 = A*x3*x3 + B*y3*y3
|
|
699
|
+
#z3 = 0
|
|
700
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
|
701
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
|
702
|
+
f1 = Face.ByVertices([v2,v1,v3])
|
|
703
|
+
faces.append(f1)
|
|
704
|
+
a1 = math.radians(uOffset)*(sides-1)
|
|
705
|
+
a2 = math.radians(360)
|
|
706
|
+
x2 = math.sin(a1)*r
|
|
707
|
+
y2 = math.cos(a1)*r
|
|
708
|
+
z2 = A*x2*x2 + B*y2*y2
|
|
709
|
+
x3 = math.sin(a2)*r
|
|
710
|
+
y3 = math.cos(a2)*r
|
|
711
|
+
z3 = A*x3*x3 + B*y3*y3
|
|
712
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
|
713
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
|
714
|
+
f1 = Face.ByVertices([v2,v1,v3])
|
|
715
|
+
faces.append(f1)
|
|
716
|
+
returnTopology = topologic.Shell.ByFaces(faces)
|
|
717
|
+
if not returnTopology:
|
|
718
|
+
returnTopology = topologic.Cluster.ByTopologies(faces)
|
|
719
|
+
vertices = []
|
|
720
|
+
_ = returnTopology.Vertices(None, vertices)
|
|
721
|
+
xList = []
|
|
722
|
+
yList = []
|
|
723
|
+
zList = []
|
|
724
|
+
for aVertex in vertices:
|
|
725
|
+
xList.append(aVertex.X())
|
|
726
|
+
yList.append(aVertex.Y())
|
|
727
|
+
zList.append(aVertex.Z())
|
|
728
|
+
minX = min(xList)
|
|
729
|
+
maxX = max(xList)
|
|
730
|
+
minY = min(yList)
|
|
731
|
+
maxY = max(yList)
|
|
732
|
+
minZ = min(zList)
|
|
733
|
+
maxZ = max(zList)
|
|
734
|
+
zeroOrigin = returnTopology.CenterOfMass()
|
|
735
|
+
xOffset = 0
|
|
736
|
+
yOffset = 0
|
|
737
|
+
zOffset = 0
|
|
738
|
+
if placement.lower() == "lowerleft":
|
|
739
|
+
xOffset = -minX
|
|
740
|
+
yOffset = -minY
|
|
741
|
+
zOffset = -minZ
|
|
742
|
+
elif placement.lower() == "bottom":
|
|
743
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
|
744
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
|
745
|
+
zOffset = -minZ
|
|
746
|
+
elif placement.lower() == "center":
|
|
747
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
|
748
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
|
749
|
+
zOffset = -(minZ + (maxZ - minZ)*0.5)
|
|
750
|
+
x1 = 0
|
|
751
|
+
y1 = 0
|
|
752
|
+
z1 = 0
|
|
753
|
+
x2 = 0 + direction[0]
|
|
754
|
+
y2 = 0 + direction[1]
|
|
755
|
+
z2 = 0 + direction[2]
|
|
756
|
+
dx = x2 - x1
|
|
757
|
+
dy = y2 - y1
|
|
758
|
+
dz = z2 - z1
|
|
759
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
760
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
761
|
+
if dist < 0.0001:
|
|
762
|
+
theta = 0
|
|
763
|
+
else:
|
|
764
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
765
|
+
zeroOrigin = topologic.Vertex.ByCoordinates(0,0,0)
|
|
766
|
+
returnTopology = topologic.TopologyUtility.Rotate(returnTopology, zeroOrigin, 0, 1, 0, theta)
|
|
767
|
+
returnTopology = topologic.TopologyUtility.Rotate(returnTopology, zeroOrigin, 0, 0, 1, phi)
|
|
768
|
+
returnTopology = topologic.TopologyUtility.Translate(returnTopology, origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset)
|
|
769
|
+
return returnTopology
|
|
770
|
+
|
|
771
|
+
@staticmethod
|
|
772
|
+
def InternalBoundaries(shell: topologic.Shell) -> topologic.Topology:
|
|
773
|
+
"""
|
|
774
|
+
Returns the internal boundaries (closed wires) of the input shell. Internal boundaries are considered holes.
|
|
775
|
+
|
|
776
|
+
Parameters
|
|
777
|
+
----------
|
|
778
|
+
shell : topologic.Shell
|
|
779
|
+
The input shell.
|
|
780
|
+
|
|
781
|
+
Returns
|
|
782
|
+
-------
|
|
783
|
+
topologic.Topology
|
|
784
|
+
The wire if a single hole or a cluster of wires if more than one hole.
|
|
785
|
+
|
|
786
|
+
"""
|
|
787
|
+
from topologicpy.Cluster import Cluster
|
|
788
|
+
edges = []
|
|
789
|
+
_ = shell.Edges(None, edges)
|
|
790
|
+
ibEdges = []
|
|
791
|
+
for anEdge in edges:
|
|
792
|
+
faces = []
|
|
793
|
+
_ = anEdge.Faces(shell, faces)
|
|
794
|
+
if len(faces) > 1:
|
|
795
|
+
ibEdges.append(anEdge)
|
|
796
|
+
return Cluster.SelfMerge(Cluster.ByTopologies(ibEdges))
|
|
797
|
+
|
|
798
|
+
@staticmethod
|
|
799
|
+
def IsClosed(shell: topologic.Shell) -> bool:
|
|
800
|
+
"""
|
|
801
|
+
Returns True if the input shell is closed. Returns False otherwise.
|
|
802
|
+
|
|
803
|
+
Parameters
|
|
804
|
+
----------
|
|
805
|
+
shell : topologic.Shell
|
|
806
|
+
The input shell.
|
|
807
|
+
|
|
808
|
+
Returns
|
|
809
|
+
-------
|
|
810
|
+
bool
|
|
811
|
+
True if the input shell is closed. False otherwise.
|
|
812
|
+
|
|
813
|
+
"""
|
|
814
|
+
return shell.IsClosed()
|
|
815
|
+
|
|
816
|
+
@staticmethod
|
|
817
|
+
def Pie(origin: topologic.Vertex = None, radiusA: float = 0.5, radiusB: float = 0.0, sides: int = 32, rings: int = 1, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
|
818
|
+
"""
|
|
819
|
+
Creates a pie shape.
|
|
820
|
+
|
|
821
|
+
Parameters
|
|
822
|
+
----------
|
|
823
|
+
origin : topologic.Vertex , optional
|
|
824
|
+
The location of the origin of the pie. The default is None which results in the pie being placed at (0,0,0).
|
|
825
|
+
radiusA : float , optional
|
|
826
|
+
The outer radius of the pie. The default is 0.5.
|
|
827
|
+
radiusB : float , optional
|
|
828
|
+
The inner radius of the pie. The default is 0.25.
|
|
829
|
+
sides : int , optional
|
|
830
|
+
The number of sides of the pie. The default is 32.
|
|
831
|
+
rings : int , optional
|
|
832
|
+
The number of rings of the pie. The default is 1.
|
|
833
|
+
fromAngle : float , optional
|
|
834
|
+
The angle in degrees from which to start creating the arc of the pie. The default is 0.
|
|
835
|
+
toAngle : float , optional
|
|
836
|
+
The angle in degrees at which to end creating the arc of the pie. The default is 360.
|
|
837
|
+
direction : list , optional
|
|
838
|
+
The vector representing the up direction of the pie. The default is [0,0,1].
|
|
839
|
+
placement : str , optional
|
|
840
|
+
The description of the placement of the origin of the pie. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
841
|
+
tolerance : float , optional
|
|
842
|
+
The desired tolerance. The default is 0.0001.
|
|
843
|
+
|
|
844
|
+
Returns
|
|
845
|
+
-------
|
|
846
|
+
topologic.Shell
|
|
847
|
+
The created pie.
|
|
848
|
+
|
|
849
|
+
"""
|
|
850
|
+
from topologicpy.Vertex import Vertex
|
|
851
|
+
from topologicpy.Face import Face
|
|
852
|
+
from topologicpy.Topology import Topology
|
|
853
|
+
if not origin:
|
|
854
|
+
origin = Vertex.ByCoordinates(0,0,0)
|
|
855
|
+
if not isinstance(origin, topologic.Vertex):
|
|
856
|
+
return None
|
|
857
|
+
if toAngle < fromAngle:
|
|
858
|
+
toAngle += 360
|
|
859
|
+
if abs(toAngle-fromAngle) < tolerance:
|
|
860
|
+
return None
|
|
861
|
+
fromAngle = math.radians(fromAngle)
|
|
862
|
+
toAngle = math.radians(toAngle)
|
|
863
|
+
angleRange = toAngle - fromAngle
|
|
864
|
+
radiusA = abs(radiusA)
|
|
865
|
+
radiusB = abs(radiusB)
|
|
866
|
+
if radiusB > radiusA:
|
|
867
|
+
temp = radiusA
|
|
868
|
+
radiusA = radiusB
|
|
869
|
+
radiusB = temp
|
|
870
|
+
if abs(radiusA - radiusB) < tolerance or radiusA < tolerance:
|
|
871
|
+
return None
|
|
872
|
+
radiusRange = radiusA - radiusB
|
|
873
|
+
sides = int(abs(math.floor(sides)))
|
|
874
|
+
if sides < 3:
|
|
875
|
+
return None
|
|
876
|
+
rings = int(abs(rings))
|
|
877
|
+
if radiusB < tolerance:
|
|
878
|
+
radiusB = 0
|
|
879
|
+
xOffset = 0
|
|
880
|
+
yOffset = 0
|
|
881
|
+
zOffset = 0
|
|
882
|
+
if placement.lower() == "lowerleft":
|
|
883
|
+
xOffset = radiusA
|
|
884
|
+
yOffset = radiusA
|
|
885
|
+
uOffset = float(angleRange)/float(sides)
|
|
886
|
+
vOffset = float(radiusRange)/float(rings)
|
|
887
|
+
faces = []
|
|
888
|
+
if radiusB > tolerance:
|
|
889
|
+
for i in range(rings):
|
|
890
|
+
r1 = radiusA - vOffset*i
|
|
891
|
+
r2 = radiusA - vOffset*(i+1)
|
|
892
|
+
for j in range(sides):
|
|
893
|
+
a1 = fromAngle + uOffset*j
|
|
894
|
+
a2 = fromAngle + uOffset*(j+1)
|
|
895
|
+
x1 = math.sin(a1)*r1
|
|
896
|
+
y1 = math.cos(a1)*r1
|
|
897
|
+
z1 = 0
|
|
898
|
+
x2 = math.sin(a1)*r2
|
|
899
|
+
y2 = math.cos(a1)*r2
|
|
900
|
+
z2 = 0
|
|
901
|
+
x3 = math.sin(a2)*r2
|
|
902
|
+
y3 = math.cos(a2)*r2
|
|
903
|
+
z3 = 0
|
|
904
|
+
x4 = math.sin(a2)*r1
|
|
905
|
+
y4 = math.cos(a2)*r1
|
|
906
|
+
z4 = 0
|
|
907
|
+
v1 = Vertex.ByCoordinates(x1,y1,z1)
|
|
908
|
+
v2 = Vertex.ByCoordinates(x2,y2,z2)
|
|
909
|
+
v3 = Vertex.ByCoordinates(x3,y3,z3)
|
|
910
|
+
v4 = Vertex.ByCoordinates(x4,y4,z4)
|
|
911
|
+
f1 = Face.ByVertices([v1,v2,v3,v4])
|
|
912
|
+
faces.append(f1)
|
|
913
|
+
else:
|
|
914
|
+
x1 = 0
|
|
915
|
+
y1 = 0
|
|
916
|
+
z1 = 0
|
|
917
|
+
v1 = Vertex.ByCoordinates(x1,y1,z1)
|
|
918
|
+
for j in range(sides):
|
|
919
|
+
a1 = fromAngle + uOffset*j
|
|
920
|
+
a2 = fromAngle + uOffset*(j+1)
|
|
921
|
+
x2 = math.sin(a1)*radiusA
|
|
922
|
+
y2 = math.cos(a1)*radiusA
|
|
923
|
+
z2 = 0
|
|
924
|
+
x3 = math.sin(a2)*radiusA
|
|
925
|
+
y3 = math.cos(a2)*radiusA
|
|
926
|
+
z3 = 0
|
|
927
|
+
v2 = Vertex.ByCoordinates(x2,y2,z2)
|
|
928
|
+
v3 = Vertex.ByCoordinates(x3,y3,z3)
|
|
929
|
+
f1 = Face.ByVertices([v2,v1,v3])
|
|
930
|
+
faces.append(f1)
|
|
931
|
+
|
|
932
|
+
shell = Shell.ByFaces(faces, tolerance)
|
|
933
|
+
if not shell:
|
|
934
|
+
return None
|
|
935
|
+
x1 = 0
|
|
936
|
+
y1 = 0
|
|
937
|
+
z1 = 0
|
|
938
|
+
x2 = 0 + direction[0]
|
|
939
|
+
y2 = 0 + direction[1]
|
|
940
|
+
z2 = 0 + direction[2]
|
|
941
|
+
dx = x2 - x1
|
|
942
|
+
dy = y2 - y1
|
|
943
|
+
dz = z2 - z1
|
|
944
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
945
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
946
|
+
if dist < tolerance:
|
|
947
|
+
theta = 0
|
|
948
|
+
else:
|
|
949
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
950
|
+
shell = Topology.Rotate(shell, origin, 0, 1, 0, theta)
|
|
951
|
+
shell = Topology.Rotate(shell, origin, 0, 0, 1, phi)
|
|
952
|
+
shell = Topology.Translate(shell, origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset)
|
|
953
|
+
return shell
|
|
954
|
+
|
|
955
|
+
@staticmethod
|
|
956
|
+
def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, uSides: int = 2, vSides: int = 2, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
|
957
|
+
"""
|
|
958
|
+
Creates a rectangle.
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
origin : topologic.Vertex , optional
|
|
963
|
+
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
|
|
964
|
+
width : float , optional
|
|
965
|
+
The width of the rectangle. The default is 1.0.
|
|
966
|
+
length : float , optional
|
|
967
|
+
The length of the rectangle. The default is 1.0.
|
|
968
|
+
uSides : int , optional
|
|
969
|
+
The number of sides along the width. The default is 2.
|
|
970
|
+
vSides : int , optional
|
|
971
|
+
The number of sides along the length. The default is 2.
|
|
972
|
+
direction : list , optional
|
|
973
|
+
The vector representing the up direction of the rectangle. The default is [0,0,1].
|
|
974
|
+
placement : str , optional
|
|
975
|
+
The description of the placement of the origin of the rectangle. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
976
|
+
tolerance : float , optional
|
|
977
|
+
The desired tolerance. The default is 0.0001.
|
|
978
|
+
|
|
979
|
+
Returns
|
|
980
|
+
-------
|
|
981
|
+
topologic.Shell
|
|
982
|
+
The created shell.
|
|
983
|
+
|
|
984
|
+
"""
|
|
985
|
+
from topologicpy.Vertex import Vertex
|
|
986
|
+
from topologicpy.Wire import Wire
|
|
987
|
+
from topologicpy.Face import Face
|
|
988
|
+
if not origin:
|
|
989
|
+
origin = Vertex.ByCoordinates(0,0,0)
|
|
990
|
+
if not isinstance(origin, topologic.Vertex):
|
|
991
|
+
return None
|
|
992
|
+
uOffset = float(width)/float(uSides)
|
|
993
|
+
vOffset = float(length)/float(vSides)
|
|
994
|
+
faces = []
|
|
995
|
+
if placement.lower() == "center":
|
|
996
|
+
wOffset = width*0.5
|
|
997
|
+
lOffset = length*0.5
|
|
998
|
+
else:
|
|
999
|
+
wOffset = 0
|
|
1000
|
+
lOffset = 0
|
|
1001
|
+
for i in range(uSides):
|
|
1002
|
+
for j in range(vSides):
|
|
1003
|
+
rOrigin = Vertex.ByCoordinates(i*uOffset - wOffset, j*vOffset - lOffset, 0)
|
|
1004
|
+
w = Wire.Rectangle(origin=rOrigin, width=uOffset, length=vOffset, direction=[0,0,1], placement="lowerleft", tolerance=tolerance)
|
|
1005
|
+
f = Face.ByWire(w)
|
|
1006
|
+
faces.append(f)
|
|
1007
|
+
shell = Shell.ByFaces(faces)
|
|
1008
|
+
x1 = origin.X()
|
|
1009
|
+
y1 = origin.Y()
|
|
1010
|
+
z1 = origin.Z()
|
|
1011
|
+
x2 = origin.X() + direction[0]
|
|
1012
|
+
y2 = origin.Y() + direction[1]
|
|
1013
|
+
z2 = origin.Z() + direction[2]
|
|
1014
|
+
dx = x2 - x1
|
|
1015
|
+
dy = y2 - y1
|
|
1016
|
+
dz = z2 - z1
|
|
1017
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
1018
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
1019
|
+
if dist < 0.0001:
|
|
1020
|
+
theta = 0
|
|
1021
|
+
else:
|
|
1022
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
1023
|
+
shell = Topology.Rotate(shell, origin, 0, 1, 0, theta)
|
|
1024
|
+
shell = Topology.Rotate(shell, origin, 0, 0, 1, phi)
|
|
1025
|
+
return shell
|
|
1026
|
+
|
|
1027
|
+
def Roof(face, degree=45, angTolerance=2, tolerance=0.001):
|
|
1028
|
+
"""
|
|
1029
|
+
Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
|
1030
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
|
1031
|
+
|
|
1032
|
+
Parameters
|
|
1033
|
+
----------
|
|
1034
|
+
face : topologic.Face
|
|
1035
|
+
The input face.
|
|
1036
|
+
degree : float , optioal
|
|
1037
|
+
The desired angle in degrees of the roof. The default is 45.
|
|
1038
|
+
angTolerance : float , optional
|
|
1039
|
+
The desired angular tolerance. The default is 2. (This is set to a larger number as it was found to work better)
|
|
1040
|
+
tolerance : float , optional
|
|
1041
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
|
1042
|
+
|
|
1043
|
+
Returns
|
|
1044
|
+
-------
|
|
1045
|
+
topologic.Shell
|
|
1046
|
+
The created roof.
|
|
1047
|
+
|
|
1048
|
+
"""
|
|
1049
|
+
from topologicpy.Vertex import Vertex
|
|
1050
|
+
from topologicpy.Wire import Wire
|
|
1051
|
+
from topologicpy.Face import Face
|
|
1052
|
+
from topologicpy.Shell import Shell
|
|
1053
|
+
from topologicpy.Cell import Cell
|
|
1054
|
+
from topologicpy.Cluster import Cluster
|
|
1055
|
+
from topologicpy.Topology import Topology
|
|
1056
|
+
from topologicpy.Dictionary import Dictionary
|
|
1057
|
+
import topologic
|
|
1058
|
+
import math
|
|
1059
|
+
|
|
1060
|
+
def nearest_vertex_2d(v, vertices, tolerance=0.001):
|
|
1061
|
+
for vertex in vertices:
|
|
1062
|
+
x2 = Vertex.X(vertex)
|
|
1063
|
+
y2 = Vertex.Y(vertex)
|
|
1064
|
+
temp_v = Vertex.ByCoordinates(x2, y2, Vertex.Z(v))
|
|
1065
|
+
if Vertex.Distance(v, temp_v) <= tolerance:
|
|
1066
|
+
return vertex
|
|
1067
|
+
return None
|
|
1068
|
+
|
|
1069
|
+
if not isinstance(face, topologic.Face):
|
|
1070
|
+
return None
|
|
1071
|
+
degree = abs(degree)
|
|
1072
|
+
if degree >= 90-tolerance:
|
|
1073
|
+
return None
|
|
1074
|
+
if degree < tolerance:
|
|
1075
|
+
return None
|
|
1076
|
+
flat_face = Face.Flatten(face)
|
|
1077
|
+
d = Topology.Dictionary(flat_face)
|
|
1078
|
+
roof = Wire.Roof(flat_face, degree)
|
|
1079
|
+
if not roof:
|
|
1080
|
+
return None
|
|
1081
|
+
shell = Shell.Skeleton(flat_face)
|
|
1082
|
+
faces = Shell.Faces(shell)
|
|
1083
|
+
|
|
1084
|
+
if not faces:
|
|
1085
|
+
return None
|
|
1086
|
+
triangles = []
|
|
1087
|
+
for face in faces:
|
|
1088
|
+
internalBoundaries = Face.InternalBoundaries(face)
|
|
1089
|
+
if len(internalBoundaries) == 0:
|
|
1090
|
+
if len(Topology.Vertices(face)) > 3:
|
|
1091
|
+
triangles += Face.Triangulate(face)
|
|
1092
|
+
else:
|
|
1093
|
+
triangles += [face]
|
|
1094
|
+
|
|
1095
|
+
roof_vertices = Topology.Vertices(roof)
|
|
1096
|
+
flat_vertices = []
|
|
1097
|
+
for rv in roof_vertices:
|
|
1098
|
+
flat_vertices.append(Vertex.ByCoordinates(Vertex.X(rv), Vertex.Y(rv), 0))
|
|
1099
|
+
|
|
1100
|
+
final_triangles = []
|
|
1101
|
+
for triangle in triangles:
|
|
1102
|
+
if len(Topology.Vertices(triangle)) > 3:
|
|
1103
|
+
triangles = Face.Triangulate(triangle)
|
|
1104
|
+
else:
|
|
1105
|
+
triangles = [triangle]
|
|
1106
|
+
final_triangles += triangles
|
|
1107
|
+
|
|
1108
|
+
final_faces = []
|
|
1109
|
+
for triangle in final_triangles:
|
|
1110
|
+
face_vertices = Topology.Vertices(triangle)
|
|
1111
|
+
top_vertices = []
|
|
1112
|
+
for sv in face_vertices:
|
|
1113
|
+
temp = nearest_vertex_2d(sv, roof_vertices, tolerance=tolerance)
|
|
1114
|
+
if temp:
|
|
1115
|
+
top_vertices.append(temp)
|
|
1116
|
+
else:
|
|
1117
|
+
top_vertices.append(sv)
|
|
1118
|
+
tri_face = Face.ByVertices(top_vertices)
|
|
1119
|
+
final_faces.append(tri_face)
|
|
1120
|
+
|
|
1121
|
+
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
|
1122
|
+
if not shell:
|
|
1123
|
+
shell = Cluster.ByTopologies(final_faces)
|
|
1124
|
+
try:
|
|
1125
|
+
shell = Topology.RemoveCoplanarFaces(shell, angTolerance=angTolerance)
|
|
1126
|
+
except:
|
|
1127
|
+
pass
|
|
1128
|
+
xTran = Dictionary.ValueAtKey(d,"xTran")
|
|
1129
|
+
yTran = Dictionary.ValueAtKey(d,"yTran")
|
|
1130
|
+
zTran = Dictionary.ValueAtKey(d,"zTran")
|
|
1131
|
+
phi = Dictionary.ValueAtKey(d,"phi")
|
|
1132
|
+
theta = Dictionary.ValueAtKey(d,"theta")
|
|
1133
|
+
shell = Topology.Rotate(shell, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
|
|
1134
|
+
shell = Topology.Rotate(shell, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
|
|
1135
|
+
shell = Topology.Translate(shell, xTran, yTran, zTran)
|
|
1136
|
+
return shell
|
|
1137
|
+
|
|
1138
|
+
@staticmethod
|
|
1139
|
+
def SelfMerge(shell: topologic.Shell, angTolerance: float = 0.1) -> topologic.Face:
|
|
1140
|
+
"""
|
|
1141
|
+
Creates a face by merging the faces of the input shell. The shell must be planar within the input angular tolerance.
|
|
1142
|
+
|
|
1143
|
+
Parameters
|
|
1144
|
+
----------
|
|
1145
|
+
shell : topologic.Shell
|
|
1146
|
+
The input shell.
|
|
1147
|
+
angTolerance : float , optional
|
|
1148
|
+
The desired angular tolerance. The default is 0.1.
|
|
1149
|
+
|
|
1150
|
+
Returns
|
|
1151
|
+
-------
|
|
1152
|
+
topologic.Face
|
|
1153
|
+
The created face.
|
|
1154
|
+
|
|
1155
|
+
"""
|
|
1156
|
+
from topologicpy.Wire import Wire
|
|
1157
|
+
from topologicpy.Face import Face
|
|
1158
|
+
from topologicpy.Shell import Shell
|
|
1159
|
+
|
|
1160
|
+
def planarizeList(wireList):
|
|
1161
|
+
returnList = []
|
|
1162
|
+
for aWire in wireList:
|
|
1163
|
+
returnList.append(Wire.Planarize(aWire))
|
|
1164
|
+
return returnList
|
|
1165
|
+
if not isinstance(shell, topologic.Shell):
|
|
1166
|
+
return None
|
|
1167
|
+
ext_boundary = Shell.ExternalBoundary(shell)
|
|
1168
|
+
if isinstance(ext_boundary, topologic.Wire):
|
|
1169
|
+
try:
|
|
1170
|
+
return topologic.Face.ByExternalBoundary(Wire.RemoveCollinearEdges(ext_boundary, angTolerance))
|
|
1171
|
+
except:
|
|
1172
|
+
try:
|
|
1173
|
+
return topologic.Face.ByExternalBoundary(Wire.Planarize(Wire.RemoveCollinearEdges(ext_boundary, angTolerance)))
|
|
1174
|
+
except:
|
|
1175
|
+
print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
|
|
1176
|
+
return None
|
|
1177
|
+
elif isinstance(ext_boundary, topologic.Cluster):
|
|
1178
|
+
wires = []
|
|
1179
|
+
_ = ext_boundary.Wires(None, wires)
|
|
1180
|
+
faces = []
|
|
1181
|
+
areas = []
|
|
1182
|
+
for aWire in wires:
|
|
1183
|
+
try:
|
|
1184
|
+
aFace = topologic.Face.ByExternalBoundary(Wire.RemoveCollinearEdges(aWire, angTolerance))
|
|
1185
|
+
except:
|
|
1186
|
+
aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Wire.RemoveCollinearEdges(aWire, angTolerance)))
|
|
1187
|
+
anArea = topologic.FaceUtility.Area(aFace)
|
|
1188
|
+
faces.append(aFace)
|
|
1189
|
+
areas.append(anArea)
|
|
1190
|
+
max_index = areas.index(max(areas))
|
|
1191
|
+
ext_boundary = faces[max_index]
|
|
1192
|
+
int_boundaries = list(set(faces) - set([ext_boundary]))
|
|
1193
|
+
int_wires = []
|
|
1194
|
+
for int_boundary in int_boundaries:
|
|
1195
|
+
temp_wires = []
|
|
1196
|
+
_ = int_boundary.Wires(None, temp_wires)
|
|
1197
|
+
int_wires.append(Wire.RemoveCollinearEdges(temp_wires[0], angTolerance))
|
|
1198
|
+
temp_wires = []
|
|
1199
|
+
_ = ext_boundary.Wires(None, temp_wires)
|
|
1200
|
+
ext_wire = Wire.RemoveCollinearEdges(temp_wires[0], angTolerance)
|
|
1201
|
+
try:
|
|
1202
|
+
return Face.ByWires(ext_wire, int_wires)
|
|
1203
|
+
except:
|
|
1204
|
+
return Face.ByWires(Wire.Planarize(ext_wire), planarizeList(int_wires))
|
|
1205
|
+
else:
|
|
1206
|
+
return None
|
|
1207
|
+
|
|
1208
|
+
def Skeleton(face, tolerance=0.001):
|
|
1209
|
+
"""
|
|
1210
|
+
Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
|
1211
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
|
1212
|
+
|
|
1213
|
+
Parameters
|
|
1214
|
+
----------
|
|
1215
|
+
face : topologic.Face
|
|
1216
|
+
The input face.
|
|
1217
|
+
tolerance : float , optional
|
|
1218
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
|
1219
|
+
|
|
1220
|
+
Returns
|
|
1221
|
+
-------
|
|
1222
|
+
topologic.Shell
|
|
1223
|
+
The created straight skeleton.
|
|
1224
|
+
|
|
1225
|
+
"""
|
|
1226
|
+
from topologicpy.Wire import Wire
|
|
1227
|
+
from topologicpy.Face import Face
|
|
1228
|
+
from topologicpy.Topology import Topology
|
|
1229
|
+
import topologic
|
|
1230
|
+
import math
|
|
1231
|
+
|
|
1232
|
+
if not isinstance(face, topologic.Face):
|
|
1233
|
+
return None
|
|
1234
|
+
roof = Wire.Skeleton(face)
|
|
1235
|
+
if not roof:
|
|
1236
|
+
return None
|
|
1237
|
+
br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
|
|
1238
|
+
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
|
|
1239
|
+
bf = Face.ByWire(br)
|
|
1240
|
+
large_shell = Topology.Boolean(bf, roof, operation="slice")
|
|
1241
|
+
if not large_shell:
|
|
1242
|
+
return None
|
|
1243
|
+
faces = Topology.Faces(large_shell)
|
|
1244
|
+
if not faces:
|
|
1245
|
+
return None
|
|
1246
|
+
final_faces = []
|
|
1247
|
+
for f in faces:
|
|
1248
|
+
internalBoundaries = Face.InternalBoundaries(f)
|
|
1249
|
+
if len(internalBoundaries) == 0:
|
|
1250
|
+
final_faces.append(f)
|
|
1251
|
+
shell = Shell.ByFaces(final_faces)
|
|
1252
|
+
return shell
|
|
1253
|
+
|
|
1254
|
+
@staticmethod
|
|
1255
|
+
def Vertices(shell: topologic.Shell) -> list:
|
|
1256
|
+
"""
|
|
1257
|
+
Returns the vertices of the input shell.
|
|
1258
|
+
|
|
1259
|
+
Parameters
|
|
1260
|
+
----------
|
|
1261
|
+
shell : topologic.Shell
|
|
1262
|
+
The input shell.
|
|
1263
|
+
|
|
1264
|
+
Returns
|
|
1265
|
+
-------
|
|
1266
|
+
list
|
|
1267
|
+
The list of vertices.
|
|
1268
|
+
|
|
1269
|
+
"""
|
|
1270
|
+
if not isinstance(shell, topologic.Shell):
|
|
1271
|
+
return None
|
|
1272
|
+
vertices = []
|
|
1273
|
+
_ = shell.Vertices(None, vertices)
|
|
1274
|
+
return vertices
|
|
1275
|
+
|
|
1276
|
+
@staticmethod
|
|
1277
|
+
def Voronoi(vertices: list, face: topologic.Face = None) -> topologic.Shell:
|
|
1278
|
+
"""
|
|
1279
|
+
Returns a voronoi partitioning of the input face based on the input vertices. The vertices must be coplanar and within the face. See https://en.wikipedia.org/wiki/Voronoi_diagram.
|
|
1280
|
+
|
|
1281
|
+
Parameters
|
|
1282
|
+
----------
|
|
1283
|
+
vertices : list
|
|
1284
|
+
The input list of vertices.
|
|
1285
|
+
face : topologic.Face , optional
|
|
1286
|
+
The input face. If the face is not set an optimised bounding rectangle of the input vertices is used instead. The default is None.
|
|
1287
|
+
|
|
1288
|
+
Returns
|
|
1289
|
+
-------
|
|
1290
|
+
shell
|
|
1291
|
+
A shell representing the voronoi partitioning of the input face.
|
|
1292
|
+
|
|
1293
|
+
"""
|
|
1294
|
+
from topologicpy.Vertex import Vertex
|
|
1295
|
+
from topologicpy.Edge import Edge
|
|
1296
|
+
from topologicpy.Wire import Wire
|
|
1297
|
+
from topologicpy.Face import Face
|
|
1298
|
+
from topologicpy.Cluster import Cluster
|
|
1299
|
+
from topologicpy.Topology import Topology
|
|
1300
|
+
from topologicpy.Dictionary import Dictionary
|
|
1301
|
+
import sys
|
|
1302
|
+
import subprocess
|
|
1303
|
+
|
|
1304
|
+
try:
|
|
1305
|
+
from scipy.spatial import Voronoi
|
|
1306
|
+
except:
|
|
1307
|
+
call = [sys.executable, '-m', 'pip', 'install', 'scipy', '-t', sys.path[0]]
|
|
1308
|
+
subprocess.run(call)
|
|
1309
|
+
try:
|
|
1310
|
+
from scipy.spatial import Voronoi
|
|
1311
|
+
except:
|
|
1312
|
+
print("Shell.Voronoi - ERROR: Could not import scipy. Returning None.")
|
|
1313
|
+
return None
|
|
1314
|
+
|
|
1315
|
+
if not isinstance(face, topologic.Face):
|
|
1316
|
+
cluster = Cluster.ByTopologies(vertices)
|
|
1317
|
+
br = Wire.BoundingRectangle(cluster, optimize=5)
|
|
1318
|
+
face = Face.ByWire(br)
|
|
1319
|
+
if not isinstance(vertices, list):
|
|
1320
|
+
return None
|
|
1321
|
+
vertices = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
|
1322
|
+
if len(vertices) < 2:
|
|
1323
|
+
return None
|
|
1324
|
+
|
|
1325
|
+
# Flatten the input face
|
|
1326
|
+
flatFace = Face.Flatten(face)
|
|
1327
|
+
# Retrieve the needed transformations
|
|
1328
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
1329
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
1330
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
1331
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
1332
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
1333
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
1334
|
+
|
|
1335
|
+
# Create a Vertex at the world's origin (0,0,0)
|
|
1336
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
1337
|
+
|
|
1338
|
+
# Create a cluster of the input vertices
|
|
1339
|
+
verticesCluster = Cluster.ByTopologies(vertices)
|
|
1340
|
+
|
|
1341
|
+
# Flatten the cluster using the same transformations
|
|
1342
|
+
verticesCluster = Topology.Translate(verticesCluster, -xTran, -yTran, -zTran)
|
|
1343
|
+
verticesCluster = Topology.Rotate(verticesCluster, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
1344
|
+
verticesCluster = Topology.Rotate(verticesCluster, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
1345
|
+
|
|
1346
|
+
flatVertices = Cluster.Vertices(verticesCluster)
|
|
1347
|
+
points = []
|
|
1348
|
+
for flatVertex in flatVertices:
|
|
1349
|
+
points.append([flatVertex.X(), flatVertex.Y()])
|
|
1350
|
+
|
|
1351
|
+
br = Wire.BoundingRectangle(flatFace)
|
|
1352
|
+
br_vertices = Wire.Vertices(br)
|
|
1353
|
+
br_x = []
|
|
1354
|
+
br_y = []
|
|
1355
|
+
for br_v in br_vertices:
|
|
1356
|
+
x, y = Vertex.Coordinates(br_v, outputType="xy")
|
|
1357
|
+
br_x.append(x)
|
|
1358
|
+
br_y.append(y)
|
|
1359
|
+
min_x = min(br_x)
|
|
1360
|
+
max_x = max(br_x)
|
|
1361
|
+
min_y = min(br_y)
|
|
1362
|
+
max_y = max(br_y)
|
|
1363
|
+
br_width = abs(max_x - min_x)
|
|
1364
|
+
br_length = abs(max_y - min_y)
|
|
1365
|
+
|
|
1366
|
+
points.append((-br_width*4, -br_length*4))
|
|
1367
|
+
points.append((-br_width*4, br_length*4))
|
|
1368
|
+
points.append((br_width*4, -br_length*4))
|
|
1369
|
+
points.append((br_width*4, br_length*4))
|
|
1370
|
+
|
|
1371
|
+
voronoi = Voronoi(points, furthest_site=False)
|
|
1372
|
+
voronoiVertices = []
|
|
1373
|
+
for v in voronoi.vertices:
|
|
1374
|
+
voronoiVertices.append(Vertex.ByCoordinates(v[0], v[1], 0))
|
|
1375
|
+
|
|
1376
|
+
faces = []
|
|
1377
|
+
for region in voronoi.regions:
|
|
1378
|
+
tempWire = []
|
|
1379
|
+
if len(region) > 1 and not -1 in region:
|
|
1380
|
+
for v in region:
|
|
1381
|
+
tempWire.append(Vertex.ByCoordinates(voronoiVertices[v].X(), voronoiVertices[v].Y(),0))
|
|
1382
|
+
faces.append(Face.ByWire(Wire.ByVertices(tempWire, close=True)))
|
|
1383
|
+
shell = Shell.ByFaces(faces)
|
|
1384
|
+
edges = Shell.Edges(shell)
|
|
1385
|
+
edgesCluster = Cluster.ByTopologies(edges)
|
|
1386
|
+
shell = Topology.Boolean(flatFace,edgesCluster, operation="slice")
|
|
1387
|
+
shell = Topology.Rotate(shell, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
1388
|
+
shell = Topology.Rotate(shell, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
1389
|
+
shell = Topology.Translate(shell, xTran, yTran, zTran)
|
|
1390
|
+
return shell
|
|
1391
|
+
|
|
1392
|
+
@staticmethod
|
|
1393
|
+
def Wires(shell: topologic.Shell) -> list:
|
|
1394
|
+
"""
|
|
1395
|
+
Returns the wires of the input shell.
|
|
1396
|
+
|
|
1397
|
+
Parameters
|
|
1398
|
+
----------
|
|
1399
|
+
shell : topologic.Shell
|
|
1400
|
+
The input shell.
|
|
1401
|
+
|
|
1402
|
+
Returns
|
|
1403
|
+
-------
|
|
1404
|
+
list
|
|
1405
|
+
The list of wires.
|
|
1406
|
+
|
|
1407
|
+
"""
|
|
1408
|
+
if not isinstance(shell, topologic.Shell):
|
|
1409
|
+
return None
|
|
1410
|
+
wires = []
|
|
1411
|
+
_ = shell.Wires(None, wires)
|
|
1412
|
+
return wires
|
|
1413
|
+
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
|