topologicpy 0.5.9__py3-none-any.whl → 6.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologicpy/Aperture.py +72 -72
- topologicpy/Cell.py +2169 -2169
- topologicpy/CellComplex.py +1137 -1137
- topologicpy/Cluster.py +1288 -1280
- topologicpy/Color.py +423 -423
- topologicpy/Context.py +79 -79
- topologicpy/DGL.py +3213 -3240
- topologicpy/Dictionary.py +698 -698
- topologicpy/Edge.py +1187 -1187
- topologicpy/EnergyModel.py +1180 -1152
- topologicpy/Face.py +2141 -2141
- topologicpy/Graph.py +7768 -7768
- topologicpy/Grid.py +353 -353
- topologicpy/Helper.py +507 -507
- topologicpy/Honeybee.py +461 -461
- topologicpy/Matrix.py +271 -271
- topologicpy/Neo4j.py +521 -521
- topologicpy/Plotly.py +2 -2
- topologicpy/Polyskel.py +541 -541
- topologicpy/Shell.py +1768 -1768
- topologicpy/Speckle.py +508 -508
- topologicpy/Topology.py +7060 -7002
- topologicpy/Vector.py +905 -905
- topologicpy/Vertex.py +1585 -1585
- topologicpy/Wire.py +3050 -3050
- topologicpy/__init__.py +22 -38
- topologicpy/version.py +1 -0
- {topologicpy-0.5.9.dist-info → topologicpy-6.0.0.dist-info}/LICENSE +661 -704
- topologicpy-6.0.0.dist-info/METADATA +751 -0
- topologicpy-6.0.0.dist-info/RECORD +32 -0
- topologicpy/bin/linux/topologic/__init__.py +0 -2
- topologicpy/bin/linux/topologic/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic/libstdc++-672d7b41.so.6.0.30 +0 -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/macos/topologic/__init__.py +0 -2
- 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 +0 -2
- 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.5.9.dist-info/METADATA +0 -86
- topologicpy-0.5.9.dist-info/RECORD +0 -91
- {topologicpy-0.5.9.dist-info → topologicpy-6.0.0.dist-info}/WHEEL +0 -0
- {topologicpy-0.5.9.dist-info → topologicpy-6.0.0.dist-info}/top_level.txt +0 -0
topologicpy/Shell.py
CHANGED
@@ -1,1769 +1,1769 @@
|
|
1
|
-
# Copyright (C) 2024
|
2
|
-
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
-
#
|
4
|
-
# This program is free software: you can redistribute it and/or modify it under
|
5
|
-
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
-
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
-
# version.
|
8
|
-
#
|
9
|
-
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
-
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
-
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
-
# details.
|
13
|
-
#
|
14
|
-
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
-
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
import topologic
|
18
|
-
from topologicpy.Topology import Topology
|
19
|
-
import math
|
20
|
-
import os
|
21
|
-
import warnings
|
22
|
-
|
23
|
-
try:
|
24
|
-
from tqdm.auto import tqdm
|
25
|
-
except:
|
26
|
-
print("Shell - Installing required tqdm library.")
|
27
|
-
try:
|
28
|
-
os.system("pip install tqdm")
|
29
|
-
except:
|
30
|
-
os.system("pip install tqdm --user")
|
31
|
-
try:
|
32
|
-
from tqdm.auto import tqdm
|
33
|
-
print("Shell - tqdm library installed correctly.")
|
34
|
-
except:
|
35
|
-
warnings.warn("Shell - Error: Could not import tqdm.")
|
36
|
-
|
37
|
-
try:
|
38
|
-
from scipy.spatial import Delaunay
|
39
|
-
from scipy.spatial import Voronoi
|
40
|
-
except:
|
41
|
-
print("Shell - Install required scipy library.")
|
42
|
-
try:
|
43
|
-
os.system("pip install scipy")
|
44
|
-
except:
|
45
|
-
os.system("pip install scipy --user")
|
46
|
-
try:
|
47
|
-
from scipy.spatial import Delaunay
|
48
|
-
from scipy.spatial import Voronoi
|
49
|
-
except:
|
50
|
-
warnings.warn("Shell - Error: Could not import scipy.")
|
51
|
-
|
52
|
-
class Shell(Topology):
|
53
|
-
@staticmethod
|
54
|
-
def ByDisjointFaces(externalBoundary, faces, maximumGap=0.5, mergeJunctions=False, threshold=0.5, uSides=1, vSides=1, transferDictionaries=False, tolerance=0.0001):
|
55
|
-
"""
|
56
|
-
Creates a shell from an input list of disjointed faces. THIS IS STILL EXPERIMENTAL
|
57
|
-
|
58
|
-
Parameters
|
59
|
-
----------
|
60
|
-
externalBoundary : topologic.Face
|
61
|
-
The input external boundary of the faces. This resembles a ribbon (face with hole) where its interior boundary touches the edges of the input list of faces.
|
62
|
-
faces : list
|
63
|
-
The input list of faces.
|
64
|
-
maximumGap : float , optional
|
65
|
-
The length of the maximum gap between the faces. The default is 0.5.
|
66
|
-
mergeJunctions : bool , optional
|
67
|
-
If set to True, the interior junctions are merged into a single vertex. Otherwise, diagonal edges are added to resolve transitions between different gap distances.
|
68
|
-
threshold : float , optional
|
69
|
-
The desired threshold under which vertices are merged into a single vertex. The default is 0.5.
|
70
|
-
uSides : int , optional
|
71
|
-
The desired number of sides along the X axis for the grid that subdivides the input faces to aid in processing. The default is 1.
|
72
|
-
vSides : int , optional
|
73
|
-
The desired number of sides along the Y axis for the grid that subdivides the input faces to aid in processing. The default is 1.
|
74
|
-
transferDictionaries : bool, optional.
|
75
|
-
If set to True, the dictionaries in the input list of faces are transfered to the faces of the resulting shell. The default is False.
|
76
|
-
tolerance : float , optional
|
77
|
-
The desired tolerance. The default is 0.0001.
|
78
|
-
|
79
|
-
Returns
|
80
|
-
-------
|
81
|
-
topologic.Shell
|
82
|
-
The created Shell.
|
83
|
-
|
84
|
-
"""
|
85
|
-
from topologicpy.Vertex import Vertex
|
86
|
-
from topologicpy.Edge import Edge
|
87
|
-
from topologicpy.Wire import Wire
|
88
|
-
from topologicpy.Face import Face
|
89
|
-
from topologicpy.Cluster import Cluster
|
90
|
-
from topologicpy.Helper import Helper
|
91
|
-
from topologicpy.Topology import Topology
|
92
|
-
from topologicpy.Grid import Grid
|
93
|
-
from topologicpy.Dictionary import Dictionary
|
94
|
-
|
95
|
-
def removeShards(edges, hostTopology, maximumGap=0.5):
|
96
|
-
returnEdges = []
|
97
|
-
for e in tqdm(edges, desc="Removing Shards", leave=False):
|
98
|
-
if Edge.Length(e) < maximumGap:
|
99
|
-
sv = Edge.StartVertex(e)
|
100
|
-
ev = Edge.EndVertex(e)
|
101
|
-
sEdges = Topology.SuperTopologies(sv, hostTopology, "edge")
|
102
|
-
sn = len(sEdges)
|
103
|
-
eEdges = Topology.SuperTopologies(ev, hostTopology, "edge")
|
104
|
-
en = len(eEdges)
|
105
|
-
if sn >= 2 and en >= 2:
|
106
|
-
returnEdges.append(e)
|
107
|
-
else:
|
108
|
-
returnEdges.append(e)
|
109
|
-
return returnEdges
|
110
|
-
|
111
|
-
def extendEdges(edges, hostTopology, maximumGap=0.5):
|
112
|
-
returnEdges = []
|
113
|
-
for e in tqdm(edges, desc="Extending Edges", leave=False):
|
114
|
-
sv = Edge.StartVertex(e)
|
115
|
-
ev = Edge.EndVertex(e)
|
116
|
-
sEdges = Topology.SuperTopologies(sv, hostTopology, "edge")
|
117
|
-
sn = len(sEdges)
|
118
|
-
eEdges = Topology.SuperTopologies(ev, hostTopology, "edge")
|
119
|
-
en = len(eEdges)
|
120
|
-
if sn == 1:
|
121
|
-
ee = Edge.Extend(e, distance=maximumGap, bothSides=False, reverse=True)
|
122
|
-
returnEdges.append(ee)
|
123
|
-
elif en == 1:
|
124
|
-
ee = Edge.Extend(e, distance=maximumGap, bothSides=False, reverse=False)
|
125
|
-
returnEdges.append(ee)
|
126
|
-
else:
|
127
|
-
returnEdges.append(e)
|
128
|
-
return returnEdges
|
129
|
-
|
130
|
-
facesCluster = Cluster.ByTopologies(faces)
|
131
|
-
internalBoundary = Face.ByWire(Face.InternalBoundaries(externalBoundary)[0], tolerance=tolerance)
|
132
|
-
bb = Topology.BoundingBox(internalBoundary)
|
133
|
-
bb_d = Topology.Dictionary(bb)
|
134
|
-
unitU = Dictionary.ValueAtKey(bb_d, 'width') / uSides
|
135
|
-
unitV = Dictionary.ValueAtKey(bb_d, 'length') / vSides
|
136
|
-
uRange = [u*unitU for u in range(uSides)]
|
137
|
-
vRange = [v*unitV for v in range(vSides)]
|
138
|
-
grid = Grid.EdgesByDistances(internalBoundary, uRange=uRange, vRange=vRange, clip=True)
|
139
|
-
grid = Topology.Slice(internalBoundary, grid, tolerance=tolerance)
|
140
|
-
grid_faces = Topology.Faces(grid)
|
141
|
-
skeletons = []
|
142
|
-
for ib in tqdm(grid_faces, desc="Processing "+str(len(grid_faces))+" tiles", leave=False):
|
143
|
-
building_shell = Topology.Slice(ib, facesCluster, tolerance=tolerance)
|
144
|
-
wall_faces = Topology.Faces(building_shell)
|
145
|
-
walls = []
|
146
|
-
for w1 in wall_faces:
|
147
|
-
iv = Topology.InternalVertex(w1, tolerance=tolerance)
|
148
|
-
flag = False
|
149
|
-
for w2 in faces:
|
150
|
-
if Vertex.IsInternal(iv, w2):
|
151
|
-
flag = True
|
152
|
-
break;
|
153
|
-
if flag == False:
|
154
|
-
walls.append(w1)
|
155
|
-
for wall in walls:
|
156
|
-
skeleton = Wire.Skeleton(wall, tolerance=0.001) # This tolerance works better.
|
157
|
-
skeleton = Topology.Difference(skeleton, facesCluster, tolerance=tolerance)
|
158
|
-
skeleton = Topology.Difference(skeleton, Face.Wire(wall), tolerance=tolerance)
|
159
|
-
skeletons.append(skeleton)
|
160
|
-
if len(skeletons) > 0:
|
161
|
-
skeleton_cluster = Cluster.ByTopologies(skeletons+[internalBoundary])
|
162
|
-
skEdges = Topology.SelfMerge(Cluster.ByTopologies(removeShards(Topology.Edges(skeleton_cluster), skeleton_cluster, maximumGap=maximumGap)), tolerance=tolerance)
|
163
|
-
if isinstance(skEdges, topologic.Edge):
|
164
|
-
skEdges = extendEdges([skEdges], skEdges, maximumGap=maximumGap)
|
165
|
-
else:
|
166
|
-
skEdges = extendEdges(Topology.Edges(skEdges), skEdges, maximumGap=maximumGap)
|
167
|
-
if len(skEdges) < 1:
|
168
|
-
print("ShellByDisjointFaces - Warning: No edges were extended.")
|
169
|
-
#return Cluster.ByTopologies(skEdges)
|
170
|
-
#print("ShellByDisjointFaces - Error: Could not derive central skeleton of interior walls. Returning None.")
|
171
|
-
#return None
|
172
|
-
|
173
|
-
shell = Topology.Slice(internalBoundary, skeleton_cluster, tolerance=tolerance)
|
174
|
-
if mergeJunctions == True:
|
175
|
-
vertices = Shell.Vertices(shell)
|
176
|
-
centers = []
|
177
|
-
used = []
|
178
|
-
for v in vertices:
|
179
|
-
for w in vertices:
|
180
|
-
if not Topology.IsSame(v, w) and not w in used:
|
181
|
-
if Vertex.Distance(v, w) < threshold:
|
182
|
-
centers.append(v)
|
183
|
-
used.append(w)
|
184
|
-
edges = Shell.Edges(shell)
|
185
|
-
new_edges = []
|
186
|
-
for e in edges:
|
187
|
-
sv = Edge.StartVertex(e)
|
188
|
-
ev = Edge.EndVertex(e)
|
189
|
-
for v in centers:
|
190
|
-
if Vertex.Distance(sv, v) < threshold:
|
191
|
-
sv = v
|
192
|
-
if Vertex.Distance(ev, v) < threshold:
|
193
|
-
ev = v
|
194
|
-
new_edges.append(Edge.ByVertices([sv,ev], tolerance=tolerance))
|
195
|
-
cluster = Cluster.ByTopologies(new_edges)
|
196
|
-
|
197
|
-
vertices = Topology.Vertices(cluster)
|
198
|
-
edges = Topology.Edges(shell)
|
199
|
-
|
200
|
-
xList = list(set([Vertex.X(v) for v in vertices]))
|
201
|
-
xList.sort()
|
202
|
-
xList = Helper.MergeByThreshold(xList, 0.5)
|
203
|
-
yList = list(set([Vertex.Y(v) for v in vertices]))
|
204
|
-
yList.sort()
|
205
|
-
yList = Helper.MergeByThreshold(yList, 0.5)
|
206
|
-
yList.sort()
|
207
|
-
|
208
|
-
centers = []
|
209
|
-
|
210
|
-
new_edges = []
|
211
|
-
|
212
|
-
for e in edges:
|
213
|
-
sv = Edge.StartVertex(e)
|
214
|
-
ev = Edge.EndVertex(e)
|
215
|
-
svx = Vertex.X(sv)
|
216
|
-
svy = Vertex.Y(sv)
|
217
|
-
evx = Vertex.X(ev)
|
218
|
-
evy = Vertex.Y(ev)
|
219
|
-
for x in xList:
|
220
|
-
if abs(svx-x) < threshold:
|
221
|
-
svx = x
|
222
|
-
break;
|
223
|
-
for y in yList:
|
224
|
-
if abs(svy-y) < threshold:
|
225
|
-
svy = y
|
226
|
-
break;
|
227
|
-
sv = Vertex.ByCoordinates(svx, svy, 0)
|
228
|
-
for x in xList:
|
229
|
-
if abs(evx-x) < threshold:
|
230
|
-
evx = x
|
231
|
-
break;
|
232
|
-
for y in yList:
|
233
|
-
if abs(evy-y) < threshold:
|
234
|
-
evy = y
|
235
|
-
break;
|
236
|
-
sv = Vertex.ByCoordinates(svx, svy, 0)
|
237
|
-
ev = Vertex.ByCoordinates(evx, evy, 0)
|
238
|
-
new_edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
|
239
|
-
|
240
|
-
cluster = Cluster.ByTopologies(new_edges)
|
241
|
-
eb = Face.ByWire(Shell.ExternalBoundary(shell), tolerance=tolerance)
|
242
|
-
shell = Topology.Slice(eb, cluster, tolerance=tolerance)
|
243
|
-
if not isinstance(shell, topologic.Shell):
|
244
|
-
try:
|
245
|
-
temp_wires = [Wire.RemoveCollinearEdges(w, angTolerance=1.0) for w in Topology.Wires(shell)]
|
246
|
-
temp_faces = [Face.ByWire(w, tolerance=tolerance) for w in temp_wires]
|
247
|
-
except:
|
248
|
-
temp_faces = Topology.Faces(shell)
|
249
|
-
shell = Shell.ByFaces(temp_faces, tolerance=tolerance)
|
250
|
-
if transferDictionaries == True:
|
251
|
-
selectors = []
|
252
|
-
for f in faces:
|
253
|
-
d = Topology.Dictionary(f)
|
254
|
-
s = Topology.InternalVertex(f, tolerance=tolerance)
|
255
|
-
s = Topology.SetDictionary(s, d)
|
256
|
-
selectors.append(s)
|
257
|
-
_ = Topology.TransferDictionariesBySelectors(topology=shell, selectors=selectors, tranFaces=True, tolerance=tolerance)
|
258
|
-
return shell
|
259
|
-
return None
|
260
|
-
|
261
|
-
@staticmethod
|
262
|
-
def ByFaces(faces: list, tolerance: float = 0.0001) -> topologic.Shell:
|
263
|
-
"""
|
264
|
-
Creates a shell from the input list of faces.
|
265
|
-
|
266
|
-
Parameters
|
267
|
-
----------
|
268
|
-
faces : list
|
269
|
-
The input list of faces.
|
270
|
-
tolerance : float , optional
|
271
|
-
The desired tolerance. The default is 0.0001.
|
272
|
-
|
273
|
-
Returns
|
274
|
-
-------
|
275
|
-
topologic.Shell
|
276
|
-
The created Shell.
|
277
|
-
|
278
|
-
"""
|
279
|
-
from topologicpy.Topology import Topology
|
280
|
-
|
281
|
-
if not isinstance(faces, list):
|
282
|
-
return None
|
283
|
-
faceList = [x for x in faces if isinstance(x, topologic.Face)]
|
284
|
-
if len(faceList) == 0:
|
285
|
-
print("Shell.ByFaces - Error: The input faces list does not contain any valid faces. Returning None.")
|
286
|
-
return None
|
287
|
-
shell = topologic.Shell.ByFaces(faceList, tolerance)
|
288
|
-
if not isinstance(shell, topologic.Shell):
|
289
|
-
shell = Topology.SelfMerge(shell, tolerance=tolerance)
|
290
|
-
if isinstance(shell, topologic.Shell):
|
291
|
-
return shell
|
292
|
-
else:
|
293
|
-
print("Shell.ByFaces - Error: Could not create shell. Returning None.")
|
294
|
-
return None
|
295
|
-
else:
|
296
|
-
return shell
|
297
|
-
|
298
|
-
@staticmethod
|
299
|
-
def ByFacesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Shell:
|
300
|
-
"""
|
301
|
-
Creates a shell from the input cluster of faces.
|
302
|
-
|
303
|
-
Parameters
|
304
|
-
----------
|
305
|
-
cluster : topologic.Cluster
|
306
|
-
The input cluster of faces.
|
307
|
-
tolerance : float , optional
|
308
|
-
The desired tolerance. The default is 0.0001.
|
309
|
-
|
310
|
-
Returns
|
311
|
-
-------
|
312
|
-
topologic.Shell
|
313
|
-
The created shell.
|
314
|
-
|
315
|
-
"""
|
316
|
-
if not isinstance(cluster, topologic.Cluster):
|
317
|
-
return None
|
318
|
-
faces = []
|
319
|
-
_ = cluster.Faces(None, faces)
|
320
|
-
return Shell.ByFaces(faces, tolerance=tolerance)
|
321
|
-
|
322
|
-
@staticmethod
|
323
|
-
def ByWires(wires: list, triangulate: bool = True, tolerance: float = 0.0001, silent: bool = False) -> topologic.Shell:
|
324
|
-
"""
|
325
|
-
Creates a shell by lofting through the input wires
|
326
|
-
Parameters
|
327
|
-
----------
|
328
|
-
wires : list
|
329
|
-
The input list of wires.
|
330
|
-
triangulate : bool , optional
|
331
|
-
If set to True, the faces will be triangulated. The default is True.
|
332
|
-
tolerance : float , optional
|
333
|
-
The desired tolerance. The default is 0.0001.
|
334
|
-
silent : bool , optional
|
335
|
-
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
|
336
|
-
|
337
|
-
Returns
|
338
|
-
-------
|
339
|
-
topologic.Shell
|
340
|
-
The creates shell.
|
341
|
-
"""
|
342
|
-
from topologicpy.Edge import Edge
|
343
|
-
from topologicpy.Wire import Wire
|
344
|
-
from topologicpy.Face import Face
|
345
|
-
if not isinstance(wires, list):
|
346
|
-
return None
|
347
|
-
wireList = [x for x in wires if isinstance(x, topologic.Wire)]
|
348
|
-
faces = []
|
349
|
-
for i in range(len(wireList)-1):
|
350
|
-
wire1 = wireList[i]
|
351
|
-
wire2 = wireList[i+1]
|
352
|
-
if wire1.Type() < topologic.Edge.Type() or wire2.Type() < topologic.Edge.Type():
|
353
|
-
return None
|
354
|
-
if wire1.Type() == topologic.Edge.Type():
|
355
|
-
w1_edges = [wire1]
|
356
|
-
else:
|
357
|
-
w1_edges = []
|
358
|
-
_ = wire1.Edges(None, w1_edges)
|
359
|
-
if wire2.Type() == topologic.Edge.Type():
|
360
|
-
w2_edges = [wire2]
|
361
|
-
else:
|
362
|
-
w2_edges = []
|
363
|
-
_ = wire2.Edges(None, w2_edges)
|
364
|
-
if len(w1_edges) != len(w2_edges):
|
365
|
-
return None
|
366
|
-
if triangulate == True:
|
367
|
-
for j in range (len(w1_edges)):
|
368
|
-
e1 = w1_edges[j]
|
369
|
-
e2 = w2_edges[j]
|
370
|
-
e3 = None
|
371
|
-
e4 = None
|
372
|
-
try:
|
373
|
-
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
374
|
-
except:
|
375
|
-
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
376
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e4], tolerance=tolerance), tolerance=tolerance))
|
377
|
-
try:
|
378
|
-
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
379
|
-
except:
|
380
|
-
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
381
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e3],tolerance=tolerance), tolerance=tolerance))
|
382
|
-
if e3 and e4:
|
383
|
-
e5 = Edge.ByVertices([e1.StartVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
384
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e5, e4], tolerance=tolerance), tolerance=tolerance))
|
385
|
-
faces.append(Face.ByWire(Wire.ByEdges([e2, e5, e3], tolerance=tolerance), tolerance=tolerance))
|
386
|
-
else:
|
387
|
-
for j in range (len(w1_edges)):
|
388
|
-
e1 = w1_edges[j]
|
389
|
-
e2 = w2_edges[j]
|
390
|
-
e3 = None
|
391
|
-
e4 = None
|
392
|
-
try:
|
393
|
-
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
394
|
-
except:
|
395
|
-
try:
|
396
|
-
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
397
|
-
except:
|
398
|
-
pass
|
399
|
-
try:
|
400
|
-
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
401
|
-
except:
|
402
|
-
try:
|
403
|
-
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
404
|
-
except:
|
405
|
-
pass
|
406
|
-
if e3 and e4:
|
407
|
-
try:
|
408
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2, e3], tolerance=tolerance), tolerance=tolerance))
|
409
|
-
except:
|
410
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2, e4], tolerance=tolerance), tolerance=tolerance))
|
411
|
-
elif e3:
|
412
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2], tolerance=tolerance), tolerance=tolerance))
|
413
|
-
elif e4:
|
414
|
-
faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2], tolerance=tolerance), tolerance=tolerance))
|
415
|
-
return Shell.ByFaces(faces, tolerance=tolerance)
|
416
|
-
|
417
|
-
@staticmethod
|
418
|
-
def ByWiresCluster(cluster: topologic.Cluster, triangulate: bool = True, tolerance: float = 0.0001, silent: bool = False) -> topologic.Shell:
|
419
|
-
"""
|
420
|
-
Creates a shell by lofting through the input cluster of wires
|
421
|
-
|
422
|
-
Parameters
|
423
|
-
----------
|
424
|
-
wires : topologic.Cluster
|
425
|
-
The input cluster of wires.
|
426
|
-
triangulate : bool , optional
|
427
|
-
If set to True, the faces will be triangulated. The default is True.
|
428
|
-
tolerance : float , optional
|
429
|
-
The desired tolerance. The default is 0.0001.
|
430
|
-
silent : bool , optional
|
431
|
-
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
|
432
|
-
|
433
|
-
Returns
|
434
|
-
-------
|
435
|
-
topologic.Shell
|
436
|
-
The creates shell.
|
437
|
-
|
438
|
-
"""
|
439
|
-
from topologicpy.Cluster import Cluster
|
440
|
-
if not cluster:
|
441
|
-
return None
|
442
|
-
if not isinstance(cluster, topologic.Cluster):
|
443
|
-
return None
|
444
|
-
wires = Cluster.Wires(cluster)
|
445
|
-
return Shell.ByWires(wires, triangulate=triangulate, tolerance=tolerance, silent=silent)
|
446
|
-
|
447
|
-
@staticmethod
|
448
|
-
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:
|
449
|
-
"""
|
450
|
-
Creates a circle.
|
451
|
-
|
452
|
-
Parameters
|
453
|
-
----------
|
454
|
-
origin : topologic.Vertex , optional
|
455
|
-
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
456
|
-
radius : float , optional
|
457
|
-
The radius of the circle. The default is 0.5.
|
458
|
-
sides : int , optional
|
459
|
-
The number of sides of the circle. The default is 32.
|
460
|
-
fromAngle : float , optional
|
461
|
-
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
462
|
-
toAngle : float , optional
|
463
|
-
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
464
|
-
direction : list , optional
|
465
|
-
The vector representing the up direction of the circle. The default is [0, 0, 1].
|
466
|
-
placement : str , optional
|
467
|
-
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".
|
468
|
-
tolerance : float , optional
|
469
|
-
The desired tolerance. The default is 0.0001.
|
470
|
-
|
471
|
-
Returns
|
472
|
-
-------
|
473
|
-
topologic.Shell
|
474
|
-
The created circle.
|
475
|
-
"""
|
476
|
-
return Shell.Pie(origin=origin, radiusA=radius, radiusB=0, sides=sides, rings=1, fromAngle=fromAngle, toAngle=toAngle, direction=direction, placement=placement, tolerance=tolerance)
|
477
|
-
|
478
|
-
@staticmethod
|
479
|
-
def Delaunay(vertices: list, face: topologic.Face = None, tolerance: float = 0.0001) -> topologic.Shell:
|
480
|
-
"""
|
481
|
-
Returns a delaunay partitioning of the input vertices. The vertices must be coplanar. See https://en.wikipedia.org/wiki/Delaunay_triangulation.
|
482
|
-
|
483
|
-
Parameters
|
484
|
-
----------
|
485
|
-
vertices : list
|
486
|
-
The input list of vertices.
|
487
|
-
face : topologic.Face , optional
|
488
|
-
The input face. If specified, the delaunay triangulation is clipped to the face.
|
489
|
-
tolerance : float , optional
|
490
|
-
The desired tolerance. The default is 0.0001.
|
491
|
-
|
492
|
-
Returns
|
493
|
-
-------
|
494
|
-
shell
|
495
|
-
A shell representing the delaunay triangulation of the input vertices.
|
496
|
-
|
497
|
-
"""
|
498
|
-
from topologicpy.Vertex import Vertex
|
499
|
-
from topologicpy.Wire import Wire
|
500
|
-
from topologicpy.Face import Face
|
501
|
-
from topologicpy.Cluster import Cluster
|
502
|
-
from topologicpy.Topology import Topology
|
503
|
-
from topologicpy.Dictionary import Dictionary
|
504
|
-
from random import sample
|
505
|
-
from scipy.spatial import Delaunay as SCIDelaunay
|
506
|
-
import numpy as np
|
507
|
-
|
508
|
-
if not isinstance(vertices, list):
|
509
|
-
return None
|
510
|
-
vertices = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
511
|
-
if len(vertices) < 3:
|
512
|
-
return None
|
513
|
-
|
514
|
-
# Create a Vertex at the world's origin (0, 0, 0)
|
515
|
-
world_origin = Vertex.Origin()
|
516
|
-
|
517
|
-
if isinstance(face, topologic.Face):
|
518
|
-
# Flatten the face
|
519
|
-
origin = Topology.Centroid(face)
|
520
|
-
normal = Face.Normal(face)
|
521
|
-
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
522
|
-
faceVertices = Face.Vertices(face)
|
523
|
-
vertices += faceVertices
|
524
|
-
|
525
|
-
# Create a cluster of the input vertices
|
526
|
-
verticesCluster = Cluster.ByTopologies(vertices)
|
527
|
-
|
528
|
-
# Flatten the cluster using the same transformations
|
529
|
-
verticesCluster = Topology.Flatten(verticesCluster, origin=origin, direction=normal)
|
530
|
-
|
531
|
-
vertices = Cluster.Vertices(verticesCluster)
|
532
|
-
points = []
|
533
|
-
for v in vertices:
|
534
|
-
points.append([Vertex.X(v), Vertex.Y(v)])
|
535
|
-
delaunay = SCIDelaunay(points)
|
536
|
-
simplices = delaunay.simplices
|
537
|
-
|
538
|
-
faces = []
|
539
|
-
for simplex in simplices:
|
540
|
-
tempTriangleVertices = []
|
541
|
-
tempTriangleVertices.append(vertices[simplex[0]])
|
542
|
-
tempTriangleVertices.append(vertices[simplex[1]])
|
543
|
-
tempTriangleVertices.append(vertices[simplex[2]])
|
544
|
-
tempFace = Face.ByWire(Wire.ByVertices(tempTriangleVertices), tolerance=tolerance)
|
545
|
-
faces.append(tempFace)
|
546
|
-
|
547
|
-
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
548
|
-
if shell == None:
|
549
|
-
shell = Cluster.ByTopologies(faces)
|
550
|
-
|
551
|
-
if isinstance(face, topologic.Face):
|
552
|
-
edges = Topology.Edges(shell)
|
553
|
-
shell = Topology.Slice(flatFace, Cluster.ByTopologies(edges))
|
554
|
-
# Get the internal boundaries of the face
|
555
|
-
wires = Face.InternalBoundaries(flatFace)
|
556
|
-
ibList = []
|
557
|
-
if len(wires) > 0:
|
558
|
-
ibList = [Face.ByWire(w) for w in wires]
|
559
|
-
cluster = Cluster.ByTopologies(ibList)
|
560
|
-
shell = Topology.Difference(shell, cluster)
|
561
|
-
shell = Topology.Unflatten(shell, origin=origin, direction=normal)
|
562
|
-
return shell
|
563
|
-
|
564
|
-
@staticmethod
|
565
|
-
def Edges(shell: topologic.Shell) -> list:
|
566
|
-
"""
|
567
|
-
Returns the edges of the input shell.
|
568
|
-
|
569
|
-
Parameters
|
570
|
-
----------
|
571
|
-
shell : topologic.Shell
|
572
|
-
The input shell.
|
573
|
-
|
574
|
-
Returns
|
575
|
-
-------
|
576
|
-
list
|
577
|
-
The list of edges.
|
578
|
-
|
579
|
-
"""
|
580
|
-
if not isinstance(shell, topologic.Shell):
|
581
|
-
return None
|
582
|
-
edges = []
|
583
|
-
_ = shell.Edges(None, edges)
|
584
|
-
return edges
|
585
|
-
|
586
|
-
@staticmethod
|
587
|
-
def ExternalBoundary(shell: topologic.Shell, tolerance: float = 0.0001) -> topologic.Wire:
|
588
|
-
"""
|
589
|
-
Returns the external boundary of the input shell.
|
590
|
-
|
591
|
-
Parameters
|
592
|
-
----------
|
593
|
-
shell : topologic.Shell
|
594
|
-
The input shell.
|
595
|
-
tolerance : float , optional
|
596
|
-
The desired tolerance. The default is 0.0001.
|
597
|
-
|
598
|
-
Returns
|
599
|
-
-------
|
600
|
-
topologic.Wire or topologic.Cluster
|
601
|
-
The external boundary of the input shell. If the shell has holes, the return value will be a cluster of wires.
|
602
|
-
|
603
|
-
"""
|
604
|
-
from topologicpy.Wire import Wire
|
605
|
-
from topologicpy.Cluster import Cluster
|
606
|
-
from topologicpy.Topology import Topology
|
607
|
-
|
608
|
-
if not isinstance(shell, topologic.Shell):
|
609
|
-
return None
|
610
|
-
edges = []
|
611
|
-
_ = shell.Edges(None, edges)
|
612
|
-
obEdges = []
|
613
|
-
for anEdge in edges:
|
614
|
-
faces = []
|
615
|
-
_ = anEdge.Faces(shell, faces)
|
616
|
-
if len(faces) == 1:
|
617
|
-
obEdges.append(anEdge)
|
618
|
-
return Topology.SelfMerge(Cluster.ByTopologies(obEdges), tolerance=tolerance)
|
619
|
-
|
620
|
-
@staticmethod
|
621
|
-
def Faces(shell: topologic.Shell) -> list:
|
622
|
-
"""
|
623
|
-
Returns the faces of the input shell.
|
624
|
-
|
625
|
-
Parameters
|
626
|
-
----------
|
627
|
-
shell : topologic.Shell
|
628
|
-
The input shell.
|
629
|
-
|
630
|
-
Returns
|
631
|
-
-------
|
632
|
-
list
|
633
|
-
The list of faces.
|
634
|
-
|
635
|
-
"""
|
636
|
-
if not isinstance(shell, topologic.Shell):
|
637
|
-
return None
|
638
|
-
faces = []
|
639
|
-
_ = shell.Faces(None, faces)
|
640
|
-
return faces
|
641
|
-
|
642
|
-
@staticmethod
|
643
|
-
def IsOnBoundary(shell: topologic.Shell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
644
|
-
"""
|
645
|
-
Returns True if the input vertex is on the boundary of the input shell. Returns False otherwise. On the boundary is defined as being on the boundary of one of the shell's external or internal boundaries
|
646
|
-
|
647
|
-
Parameters
|
648
|
-
----------
|
649
|
-
shell : topologic.Shell
|
650
|
-
The input shell.
|
651
|
-
vertex : topologic.Vertex
|
652
|
-
The input vertex.
|
653
|
-
tolerance : float , optional
|
654
|
-
The desired tolerance. The default is 0.0001.
|
655
|
-
|
656
|
-
Returns
|
657
|
-
-------
|
658
|
-
bool
|
659
|
-
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
660
|
-
|
661
|
-
"""
|
662
|
-
|
663
|
-
from topologicpy.Vertex import Vertex
|
664
|
-
|
665
|
-
if not isinstance(shell, topologic.Shell):
|
666
|
-
return None
|
667
|
-
if not isinstance(vertex, topologic.Vertex):
|
668
|
-
return None
|
669
|
-
boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
|
670
|
-
if Vertex.IsInternal(vertex, boundary, tolerance=tolerance):
|
671
|
-
return True
|
672
|
-
internal_boundaries = Shell.InternalBoundaries(shell, tolerance=tolerance)
|
673
|
-
for ib in internal_boundaries:
|
674
|
-
if Vertex.IsInternal(vertex, ib, tolerance=tolerance):
|
675
|
-
return True
|
676
|
-
return False
|
677
|
-
|
678
|
-
@staticmethod
|
679
|
-
def HyperbolicParaboloidRectangularDomain(origin: topologic.Vertex = None, llVertex: topologic.Vertex = None, lrVertex: topologic.Vertex =None, ulVertex: topologic.Vertex =None, urVertex: topologic.Vertex = None,
|
680
|
-
uSides: int = 10, vSides: int = 10, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
681
|
-
"""
|
682
|
-
Creates a hyperbolic paraboloid with a rectangular domain.
|
683
|
-
|
684
|
-
Parameters
|
685
|
-
----------
|
686
|
-
origin : topologic.Vertex , optional
|
687
|
-
The origin of the hyperbolic parabolid. If set to None, it will be placed at the (0, 0, 0) origin. The default is None.
|
688
|
-
llVertex : topologic.Vertex , optional
|
689
|
-
The lower left corner of the hyperbolic parabolid. If set to None, it will be set to (-0.5, -0.5, -0.5).
|
690
|
-
lrVertex : topologic.Vertex , optional
|
691
|
-
The lower right corner of the hyperbolic parabolid. If set to None, it will be set to (0.5, -0.5, 0.5).
|
692
|
-
ulVertex : topologic.Vertex , optional
|
693
|
-
The upper left corner of the hyperbolic parabolid. If set to None, it will be set to (-0.5, 0.5, 0.5).
|
694
|
-
urVertex : topologic.Vertex , optional
|
695
|
-
The upper right corner of the hyperbolic parabolid. If set to None, it will be set to (0.5, 0.5, -0.5).
|
696
|
-
uSides : int , optional
|
697
|
-
The number of segments along the X axis. The default is 10.
|
698
|
-
vSides : int , optional
|
699
|
-
The number of segments along the Y axis. The default is 10.
|
700
|
-
direction : list , optional
|
701
|
-
The vector representing the up direction of the hyperbolic parabolid. The default is [0, 0, 1].
|
702
|
-
placement : str , optional
|
703
|
-
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".
|
704
|
-
tolerance : float , optional
|
705
|
-
The desired tolerance. The default is 0.0001.
|
706
|
-
Returns
|
707
|
-
-------
|
708
|
-
topologic.Shell
|
709
|
-
The created hyperbolic paraboloid.
|
710
|
-
|
711
|
-
"""
|
712
|
-
from topologicpy.Vertex import Vertex
|
713
|
-
from topologicpy.Edge import Edge
|
714
|
-
from topologicpy.Face import Face
|
715
|
-
from topologicpy.Topology import Topology
|
716
|
-
if not isinstance(origin, topologic.Vertex):
|
717
|
-
origin = Vertex.ByCoordinates(0, 0, 0)
|
718
|
-
if not isinstance(llVertex, topologic.Vertex):
|
719
|
-
llVertex = Vertex.ByCoordinates(-0.5, -0.5, -0.5)
|
720
|
-
if not isinstance(lrVertex, topologic.Vertex):
|
721
|
-
lrVertex = Vertex.ByCoordinates(0.5, -0.5, 0.5)
|
722
|
-
if not isinstance(ulVertex, topologic.Vertex):
|
723
|
-
ulVertex = Vertex.ByCoordinates(-0.5, 0.5, 0.5)
|
724
|
-
if not isinstance(urVertex, topologic.Vertex):
|
725
|
-
urVertex = Vertex.ByCoordinates(0.5, 0.5, -0.5)
|
726
|
-
e1 = Edge.ByVertices([llVertex, lrVertex], tolerance=tolerance)
|
727
|
-
e3 = Edge.ByVertices([urVertex, ulVertex], tolerance=tolerance)
|
728
|
-
edges = []
|
729
|
-
for i in range(uSides+1):
|
730
|
-
v1 = Edge.VertexByParameter(e1, float(i)/float(uSides))
|
731
|
-
v2 = Edge.VertexByParameter(e3, 1.0 - float(i)/float(uSides))
|
732
|
-
edges.append(Edge.ByVertices([v1, v2], tolerance=tolerance))
|
733
|
-
faces = []
|
734
|
-
for i in range(uSides):
|
735
|
-
for j in range(vSides):
|
736
|
-
v1 = Edge.VertexByParameter(edges[i], float(j)/float(vSides))
|
737
|
-
v2 = Edge.VertexByParameter(edges[i], float(j+1)/float(vSides))
|
738
|
-
v3 = Edge.VertexByParameter(edges[i+1], float(j+1)/float(vSides))
|
739
|
-
v4 = Edge.VertexByParameter(edges[i+1], float(j)/float(vSides))
|
740
|
-
faces.append(Face.ByVertices([v1, v2, v4]))
|
741
|
-
faces.append(Face.ByVertices([v4, v2, v3]))
|
742
|
-
returnTopology = Shell.ByFaces(faces, tolerance=tolerance)
|
743
|
-
if not returnTopology:
|
744
|
-
returnTopology = None
|
745
|
-
xOffset = 0
|
746
|
-
yOffset = 0
|
747
|
-
zOffset = 0
|
748
|
-
minX = min([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()])
|
749
|
-
maxX = max([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()])
|
750
|
-
minY = min([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()])
|
751
|
-
maxY = max([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()])
|
752
|
-
minZ = min([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()])
|
753
|
-
maxZ = max([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()])
|
754
|
-
if placement.lower() == "lowerleft":
|
755
|
-
xOffset = -minX
|
756
|
-
yOffset = -minY
|
757
|
-
zOffset = -minZ
|
758
|
-
elif placement.lower() == "bottom":
|
759
|
-
xOffset = -(minX + (maxX - minX)*0.5)
|
760
|
-
yOffset = -(minY + (maxY - minY)*0.5)
|
761
|
-
zOffset = -minZ
|
762
|
-
elif placement.lower() == "center":
|
763
|
-
xOffset = -(minX + (maxX - minX)*0.5)
|
764
|
-
yOffset = -(minY + (maxY - minY)*0.5)
|
765
|
-
zOffset = -(minZ + (maxZ - minZ)*0.5)
|
766
|
-
returnTopology = Topology.Translate(returnTopology, xOffset, yOffset, zOffset)
|
767
|
-
returnTopology = Topology.Place(returnTopology, originA=Vertex.Origin(), originB=origin)
|
768
|
-
returnTopology = Topology.Orient(returnTopology, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
769
|
-
return returnTopology
|
770
|
-
|
771
|
-
@staticmethod
|
772
|
-
def HyperbolicParaboloidCircularDomain(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 36, rings: int = 10,
|
773
|
-
A: float = 2.0, B: float = -2.0, direction: list = [0, 0, 1],
|
774
|
-
placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
775
|
-
"""
|
776
|
-
Creates a hyperbolic paraboloid with a circular domain. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
777
|
-
|
778
|
-
Parameters
|
779
|
-
----------
|
780
|
-
origin : topologic.Vertex , optional
|
781
|
-
The origin of the hyperbolic parabolid. If set to None, it will be placed at the (0, 0, 0) origin. The default is None.
|
782
|
-
radius : float , optional
|
783
|
-
The desired radius of the hyperbolic paraboloid. The default is 0.5.
|
784
|
-
sides : int , optional
|
785
|
-
The desired number of sides of the hyperbolic parabolid. The default is 36.
|
786
|
-
rings : int , optional
|
787
|
-
The desired number of concentric rings of the hyperbolic parabolid. The default is 10.
|
788
|
-
A : float , optional
|
789
|
-
The *A* constant in the equation z = A*x^2^ + B*y^2^. The default is 2.0.
|
790
|
-
B : float , optional
|
791
|
-
The *B* constant in the equation z = A*x^2^ + B*y^2^. The default is -2.0.
|
792
|
-
direction : list , optional
|
793
|
-
The vector representing the up direction of the hyperbolic paraboloid. The default is [0, 0, 1].
|
794
|
-
placement : str , optional
|
795
|
-
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".
|
796
|
-
tolerance : float , optional
|
797
|
-
The desired tolerance. The default is 0.0001.
|
798
|
-
Returns
|
799
|
-
-------
|
800
|
-
topologic.Shell
|
801
|
-
The created hyperboloic paraboloid.
|
802
|
-
|
803
|
-
"""
|
804
|
-
from topologicpy.Vertex import Vertex
|
805
|
-
from topologicpy.Face import Face
|
806
|
-
from topologicpy.Topology import Topology
|
807
|
-
if not isinstance(origin, topologic.Vertex):
|
808
|
-
origin = Vertex.ByCoordinates(0, 0, 0)
|
809
|
-
uOffset = float(360)/float(sides)
|
810
|
-
vOffset = float(radius)/float(rings)
|
811
|
-
faces = []
|
812
|
-
for i in range(rings-1):
|
813
|
-
r1 = radius - vOffset*i
|
814
|
-
r2 = radius - vOffset*(i+1)
|
815
|
-
for j in range(sides-1):
|
816
|
-
a1 = math.radians(uOffset)*j
|
817
|
-
a2 = math.radians(uOffset)*(j+1)
|
818
|
-
x1 = math.sin(a1)*r1
|
819
|
-
y1 = math.cos(a1)*r1
|
820
|
-
z1 = A*x1*x1 + B*y1*y1
|
821
|
-
x2 = math.sin(a1)*r2
|
822
|
-
y2 = math.cos(a1)*r2
|
823
|
-
z2 = A*x2*x2 + B*y2*y2
|
824
|
-
x3 = math.sin(a2)*r2
|
825
|
-
y3 = math.cos(a2)*r2
|
826
|
-
z3 = A*x3*x3 + B*y3*y3
|
827
|
-
x4 = math.sin(a2)*r1
|
828
|
-
y4 = math.cos(a2)*r1
|
829
|
-
z4 = A*x4*x4 + B*y4*y4
|
830
|
-
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
831
|
-
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
832
|
-
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
833
|
-
v4 = topologic.Vertex.ByCoordinates(x4,y4,z4)
|
834
|
-
f1 = Face.ByVertices([v1,v2,v4])
|
835
|
-
f2 = Face.ByVertices([v4,v2,v3])
|
836
|
-
faces.append(f1)
|
837
|
-
faces.append(f2)
|
838
|
-
a1 = math.radians(uOffset)*(sides-1)
|
839
|
-
a2 = math.radians(360)
|
840
|
-
x1 = math.sin(a1)*r1
|
841
|
-
y1 = math.cos(a1)*r1
|
842
|
-
z1 = A*x1*x1 + B*y1*y1
|
843
|
-
x2 = math.sin(a1)*r2
|
844
|
-
y2 = math.cos(a1)*r2
|
845
|
-
z2 = A*x2*x2 + B*y2*y2
|
846
|
-
x3 = math.sin(a2)*r2
|
847
|
-
y3 = math.cos(a2)*r2
|
848
|
-
z3 = A*x3*x3 + B*y3*y3
|
849
|
-
x4 = math.sin(a2)*r1
|
850
|
-
y4 = math.cos(a2)*r1
|
851
|
-
z4 = A*x4*x4 + B*y4*y4
|
852
|
-
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
853
|
-
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
854
|
-
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
855
|
-
v4 = topologic.Vertex.ByCoordinates(x4,y4,z4)
|
856
|
-
f1 = Face.ByVertices([v1,v2,v4])
|
857
|
-
f2 = Face.ByVertices([v4,v2,v3])
|
858
|
-
faces.append(f1)
|
859
|
-
faces.append(f2)
|
860
|
-
# Special Case: Center triangles
|
861
|
-
r = vOffset
|
862
|
-
x1 = 0
|
863
|
-
y1 = 0
|
864
|
-
z1 = 0
|
865
|
-
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
866
|
-
for j in range(sides-1):
|
867
|
-
a1 = math.radians(uOffset)*j
|
868
|
-
a2 = math.radians(uOffset)*(j+1)
|
869
|
-
x2 = math.sin(a1)*r
|
870
|
-
y2 = math.cos(a1)*r
|
871
|
-
z2 = A*x2*x2 + B*y2*y2
|
872
|
-
#z2 = 0
|
873
|
-
x3 = math.sin(a2)*r
|
874
|
-
y3 = math.cos(a2)*r
|
875
|
-
z3 = A*x3*x3 + B*y3*y3
|
876
|
-
#z3 = 0
|
877
|
-
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
878
|
-
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
879
|
-
f1 = Face.ByVertices([v2,v1,v3])
|
880
|
-
faces.append(f1)
|
881
|
-
a1 = math.radians(uOffset)*(sides-1)
|
882
|
-
a2 = math.radians(360)
|
883
|
-
x2 = math.sin(a1)*r
|
884
|
-
y2 = math.cos(a1)*r
|
885
|
-
z2 = A*x2*x2 + B*y2*y2
|
886
|
-
x3 = math.sin(a2)*r
|
887
|
-
y3 = math.cos(a2)*r
|
888
|
-
z3 = A*x3*x3 + B*y3*y3
|
889
|
-
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
890
|
-
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
891
|
-
f1 = Face.ByVertices([v2,v1,v3])
|
892
|
-
faces.append(f1)
|
893
|
-
returnTopology = topologic.Shell.ByFaces(faces, tolerance)
|
894
|
-
if not returnTopology:
|
895
|
-
returnTopology = topologic.Cluster.ByTopologies(faces)
|
896
|
-
vertices = []
|
897
|
-
_ = returnTopology.Vertices(None, vertices)
|
898
|
-
xList = []
|
899
|
-
yList = []
|
900
|
-
zList = []
|
901
|
-
for aVertex in vertices:
|
902
|
-
xList.append(aVertex.X())
|
903
|
-
yList.append(aVertex.Y())
|
904
|
-
zList.append(aVertex.Z())
|
905
|
-
minX = min(xList)
|
906
|
-
maxX = max(xList)
|
907
|
-
minY = min(yList)
|
908
|
-
maxY = max(yList)
|
909
|
-
minZ = min(zList)
|
910
|
-
maxZ = max(zList)
|
911
|
-
xOffset = 0
|
912
|
-
yOffset = 0
|
913
|
-
zOffset = 0
|
914
|
-
if placement.lower() == "lowerleft":
|
915
|
-
xOffset = -minX
|
916
|
-
yOffset = -minY
|
917
|
-
zOffset = -minZ
|
918
|
-
elif placement.lower() == "bottom":
|
919
|
-
xOffset = -(minX + (maxX - minX)*0.5)
|
920
|
-
yOffset = -(minY + (maxY - minY)*0.5)
|
921
|
-
zOffset = -minZ
|
922
|
-
elif placement.lower() == "center":
|
923
|
-
xOffset = -(minX + (maxX - minX)*0.5)
|
924
|
-
yOffset = -(minY + (maxY - minY)*0.5)
|
925
|
-
zOffset = -(minZ + (maxZ - minZ)*0.5)
|
926
|
-
returnTopology = Topology.Translate(returnTopology, xOffset, yOffset, zOffset)
|
927
|
-
returnTopology = Topology.Place(returnTopology, originA=Vertex.Origin(), originB=origin)
|
928
|
-
returnTopology = Topology.Orient(returnTopology, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
929
|
-
return returnTopology
|
930
|
-
|
931
|
-
@staticmethod
|
932
|
-
def InternalBoundaries(shell: topologic.Shell, tolerance=0.0001) -> topologic.Topology:
|
933
|
-
"""
|
934
|
-
Returns the internal boundaries (closed wires) of the input shell. Internal boundaries are considered holes.
|
935
|
-
|
936
|
-
Parameters
|
937
|
-
----------
|
938
|
-
shell : topologic.Shell
|
939
|
-
The input shell.
|
940
|
-
tolerance : float , optional
|
941
|
-
The desired tolerance. The default is 0.0001.
|
942
|
-
|
943
|
-
Returns
|
944
|
-
-------
|
945
|
-
list
|
946
|
-
The list of internal boundaries
|
947
|
-
|
948
|
-
"""
|
949
|
-
from topologicpy.Cluster import Cluster
|
950
|
-
from topologicpy.Topology import Topology
|
951
|
-
edges = []
|
952
|
-
_ = shell.Edges(None, edges)
|
953
|
-
ibEdges = []
|
954
|
-
for anEdge in edges:
|
955
|
-
faces = []
|
956
|
-
_ = anEdge.Faces(shell, faces)
|
957
|
-
if len(faces) > 1:
|
958
|
-
ibEdges.append(anEdge)
|
959
|
-
returnTopology = Topology.SelfMerge(Cluster.ByTopologies(ibEdges), tolerance=tolerance)
|
960
|
-
wires = Topology.Wires(returnTopology)
|
961
|
-
return wires
|
962
|
-
|
963
|
-
|
964
|
-
@staticmethod
|
965
|
-
def IsClosed(shell: topologic.Shell) -> bool:
|
966
|
-
"""
|
967
|
-
Returns True if the input shell is closed. Returns False otherwise.
|
968
|
-
|
969
|
-
Parameters
|
970
|
-
----------
|
971
|
-
shell : topologic.Shell
|
972
|
-
The input shell.
|
973
|
-
|
974
|
-
Returns
|
975
|
-
-------
|
976
|
-
bool
|
977
|
-
True if the input shell is closed. False otherwise.
|
978
|
-
|
979
|
-
"""
|
980
|
-
return shell.IsClosed()
|
981
|
-
|
982
|
-
@staticmethod
|
983
|
-
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:
|
984
|
-
"""
|
985
|
-
Creates a pie shape.
|
986
|
-
|
987
|
-
Parameters
|
988
|
-
----------
|
989
|
-
origin : topologic.Vertex , optional
|
990
|
-
The location of the origin of the pie. The default is None which results in the pie being placed at (0, 0, 0).
|
991
|
-
radiusA : float , optional
|
992
|
-
The outer radius of the pie. The default is 0.5.
|
993
|
-
radiusB : float , optional
|
994
|
-
The inner radius of the pie. The default is 0.25.
|
995
|
-
sides : int , optional
|
996
|
-
The number of sides of the pie. The default is 32.
|
997
|
-
rings : int , optional
|
998
|
-
The number of rings of the pie. The default is 1.
|
999
|
-
fromAngle : float , optional
|
1000
|
-
The angle in degrees from which to start creating the arc of the pie. The default is 0.
|
1001
|
-
toAngle : float , optional
|
1002
|
-
The angle in degrees at which to end creating the arc of the pie. The default is 360.
|
1003
|
-
direction : list , optional
|
1004
|
-
The vector representing the up direction of the pie. The default is [0, 0, 1].
|
1005
|
-
placement : str , optional
|
1006
|
-
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".
|
1007
|
-
tolerance : float , optional
|
1008
|
-
The desired tolerance. The default is 0.0001.
|
1009
|
-
|
1010
|
-
Returns
|
1011
|
-
-------
|
1012
|
-
topologic.Shell
|
1013
|
-
The created pie.
|
1014
|
-
|
1015
|
-
"""
|
1016
|
-
from topologicpy.Vertex import Vertex
|
1017
|
-
from topologicpy.Face import Face
|
1018
|
-
from topologicpy.Topology import Topology
|
1019
|
-
if not origin:
|
1020
|
-
origin = Vertex.ByCoordinates(0, 0, 0)
|
1021
|
-
if not isinstance(origin, topologic.Vertex):
|
1022
|
-
return None
|
1023
|
-
if toAngle < fromAngle:
|
1024
|
-
toAngle += 360
|
1025
|
-
if abs(toAngle-fromAngle) < tolerance:
|
1026
|
-
return None
|
1027
|
-
fromAngle = math.radians(fromAngle)
|
1028
|
-
toAngle = math.radians(toAngle)
|
1029
|
-
angleRange = toAngle - fromAngle
|
1030
|
-
radiusA = abs(radiusA)
|
1031
|
-
radiusB = abs(radiusB)
|
1032
|
-
if radiusB > radiusA:
|
1033
|
-
temp = radiusA
|
1034
|
-
radiusA = radiusB
|
1035
|
-
radiusB = temp
|
1036
|
-
if abs(radiusA - radiusB) < tolerance or radiusA < tolerance:
|
1037
|
-
return None
|
1038
|
-
radiusRange = radiusA - radiusB
|
1039
|
-
sides = int(abs(math.floor(sides)))
|
1040
|
-
if sides < 3:
|
1041
|
-
return None
|
1042
|
-
rings = int(abs(rings))
|
1043
|
-
if radiusB < tolerance:
|
1044
|
-
radiusB = 0
|
1045
|
-
xOffset = 0
|
1046
|
-
yOffset = 0
|
1047
|
-
zOffset = 0
|
1048
|
-
if placement.lower() == "lowerleft":
|
1049
|
-
xOffset = radiusA
|
1050
|
-
yOffset = radiusA
|
1051
|
-
uOffset = float(angleRange)/float(sides)
|
1052
|
-
vOffset = float(radiusRange)/float(rings)
|
1053
|
-
faces = []
|
1054
|
-
if radiusB > tolerance:
|
1055
|
-
for i in range(rings):
|
1056
|
-
r1 = radiusA - vOffset*i
|
1057
|
-
r2 = radiusA - vOffset*(i+1)
|
1058
|
-
for j in range(sides):
|
1059
|
-
a1 = fromAngle + uOffset*j
|
1060
|
-
a2 = fromAngle + uOffset*(j+1)
|
1061
|
-
x1 = math.sin(a1)*r1
|
1062
|
-
y1 = math.cos(a1)*r1
|
1063
|
-
z1 = 0
|
1064
|
-
x2 = math.sin(a1)*r2
|
1065
|
-
y2 = math.cos(a1)*r2
|
1066
|
-
z2 = 0
|
1067
|
-
x3 = math.sin(a2)*r2
|
1068
|
-
y3 = math.cos(a2)*r2
|
1069
|
-
z3 = 0
|
1070
|
-
x4 = math.sin(a2)*r1
|
1071
|
-
y4 = math.cos(a2)*r1
|
1072
|
-
z4 = 0
|
1073
|
-
v1 = Vertex.ByCoordinates(x1,y1,z1)
|
1074
|
-
v2 = Vertex.ByCoordinates(x2,y2,z2)
|
1075
|
-
v3 = Vertex.ByCoordinates(x3,y3,z3)
|
1076
|
-
v4 = Vertex.ByCoordinates(x4,y4,z4)
|
1077
|
-
f1 = Face.ByVertices([v1,v2,v3,v4])
|
1078
|
-
faces.append(f1)
|
1079
|
-
else:
|
1080
|
-
x1 = 0
|
1081
|
-
y1 = 0
|
1082
|
-
z1 = 0
|
1083
|
-
v1 = Vertex.ByCoordinates(x1,y1,z1)
|
1084
|
-
for j in range(sides):
|
1085
|
-
a1 = fromAngle + uOffset*j
|
1086
|
-
a2 = fromAngle + uOffset*(j+1)
|
1087
|
-
x2 = math.sin(a1)*radiusA
|
1088
|
-
y2 = math.cos(a1)*radiusA
|
1089
|
-
z2 = 0
|
1090
|
-
x3 = math.sin(a2)*radiusA
|
1091
|
-
y3 = math.cos(a2)*radiusA
|
1092
|
-
z3 = 0
|
1093
|
-
v2 = Vertex.ByCoordinates(x2,y2,z2)
|
1094
|
-
v3 = Vertex.ByCoordinates(x3,y3,z3)
|
1095
|
-
f1 = Face.ByVertices([v2,v1,v3])
|
1096
|
-
faces.append(f1)
|
1097
|
-
|
1098
|
-
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
1099
|
-
if not shell:
|
1100
|
-
return None
|
1101
|
-
shell = Topology.Translate(shell, xOffset, yOffset, zOffset)
|
1102
|
-
shell = Topology.Place(shell, originA=Vertex.Origin(), originB=origin)
|
1103
|
-
shell = Topology.Orient(shell, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
1104
|
-
return shell
|
1105
|
-
|
1106
|
-
|
1107
|
-
@staticmethod
|
1108
|
-
def Planarize(shell: topologic.Shell, origin: topologic.Vertex = None, mantissa: int = 6, tolerance: float = 0.0001) -> topologic.Shell:
|
1109
|
-
"""
|
1110
|
-
Returns a planarized version of the input shell.
|
1111
|
-
|
1112
|
-
Parameters
|
1113
|
-
----------
|
1114
|
-
shell : topologic.Shell
|
1115
|
-
The input shell.
|
1116
|
-
tolerance : float, optional
|
1117
|
-
The desired tolerance. The default is 0.0001.
|
1118
|
-
origin : topologic.Vertex , optional
|
1119
|
-
The desired origin of the plane unto which the planar shell will be projected. If set to None, the centroid of the input shell will be chosen. The default is None.
|
1120
|
-
mantissa : int , optional
|
1121
|
-
The desired length of the mantissa. The default is 6.
|
1122
|
-
|
1123
|
-
Returns
|
1124
|
-
-------
|
1125
|
-
topologic.Shell
|
1126
|
-
The planarized shell.
|
1127
|
-
|
1128
|
-
"""
|
1129
|
-
from topologicpy.Vertex import Vertex
|
1130
|
-
from topologicpy.Face import Face
|
1131
|
-
from topologicpy.Cluster import Cluster
|
1132
|
-
from topologicpy.Topology import Topology
|
1133
|
-
|
1134
|
-
if not isinstance(shell, topologic.Shell):
|
1135
|
-
print("Shell.Planarize - Error: The input wire parameter is not a valid topologic shell. Returning None.")
|
1136
|
-
return None
|
1137
|
-
if origin == None:
|
1138
|
-
origin = Vertex.Origin()
|
1139
|
-
if not isinstance(origin, topologic.Vertex):
|
1140
|
-
print("Shell.Planarize - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
|
1141
|
-
return None
|
1142
|
-
|
1143
|
-
vertices = Topology.Vertices(shell)
|
1144
|
-
faces = Topology.Faces(shell)
|
1145
|
-
plane_equation = Vertex.PlaneEquation(vertices, mantissa=mantissa)
|
1146
|
-
rect = Face.RectangleByPlaneEquation(origin=origin , equation=plane_equation, tolerance=tolerance)
|
1147
|
-
new_vertices = [Vertex.Project(v, rect, mantissa=mantissa) for v in vertices]
|
1148
|
-
new_shell = Topology.ReplaceVertices(shell, verticesA=vertices, verticesB=new_vertices)
|
1149
|
-
new_faces = Topology.Faces(new_shell)
|
1150
|
-
return Topology.SelfMerge(Cluster.ByTopologies(new_faces), tolerance=tolerance)
|
1151
|
-
|
1152
|
-
@staticmethod
|
1153
|
-
def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0,
|
1154
|
-
uSides: int = 2, vSides: int = 2, direction: list = [0, 0, 1],
|
1155
|
-
placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
1156
|
-
"""
|
1157
|
-
Creates a rectangle.
|
1158
|
-
|
1159
|
-
Parameters
|
1160
|
-
----------
|
1161
|
-
origin : topologic.Vertex , optional
|
1162
|
-
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0).
|
1163
|
-
width : float , optional
|
1164
|
-
The width of the rectangle. The default is 1.0.
|
1165
|
-
length : float , optional
|
1166
|
-
The length of the rectangle. The default is 1.0.
|
1167
|
-
uSides : int , optional
|
1168
|
-
The number of sides along the width. The default is 2.
|
1169
|
-
vSides : int , optional
|
1170
|
-
The number of sides along the length. The default is 2.
|
1171
|
-
direction : list , optional
|
1172
|
-
The vector representing the up direction of the rectangle. The default is [0, 0, 1].
|
1173
|
-
placement : str , optional
|
1174
|
-
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".
|
1175
|
-
tolerance : float , optional
|
1176
|
-
The desired tolerance. The default is 0.0001.
|
1177
|
-
|
1178
|
-
Returns
|
1179
|
-
-------
|
1180
|
-
topologic.Shell
|
1181
|
-
The created shell.
|
1182
|
-
|
1183
|
-
"""
|
1184
|
-
from topologicpy.Vertex import Vertex
|
1185
|
-
from topologicpy.Wire import Wire
|
1186
|
-
from topologicpy.Face import Face
|
1187
|
-
if not origin:
|
1188
|
-
origin = Vertex.ByCoordinates(0, 0, 0)
|
1189
|
-
if not isinstance(origin, topologic.Vertex):
|
1190
|
-
return None
|
1191
|
-
uOffset = float(width)/float(uSides)
|
1192
|
-
vOffset = float(length)/float(vSides)
|
1193
|
-
faces = []
|
1194
|
-
if placement.lower() == "center":
|
1195
|
-
wOffset = width*0.5
|
1196
|
-
lOffset = length*0.5
|
1197
|
-
else:
|
1198
|
-
wOffset = 0
|
1199
|
-
lOffset = 0
|
1200
|
-
for i in range(uSides):
|
1201
|
-
for j in range(vSides):
|
1202
|
-
rOrigin = Vertex.ByCoordinates(i*uOffset - wOffset, j*vOffset - lOffset, 0)
|
1203
|
-
w = Wire.Rectangle(origin=rOrigin, width=uOffset, length=vOffset, direction=[0, 0, 1], placement="lowerleft", tolerance=tolerance)
|
1204
|
-
f = Face.ByWire(w, tolerance=tolerance)
|
1205
|
-
faces.append(f)
|
1206
|
-
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
1207
|
-
shell = Topology.Place(shell, originA=Vertex.Origin(), originB=origin)
|
1208
|
-
shell = Topology.Orient(shell, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
1209
|
-
return shell
|
1210
|
-
|
1211
|
-
@staticmethod
|
1212
|
-
def RemoveCollinearEdges(shell: topologic.Shell, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
|
1213
|
-
"""
|
1214
|
-
Removes any collinear edges in the input shell.
|
1215
|
-
|
1216
|
-
Parameters
|
1217
|
-
----------
|
1218
|
-
shell : topologic.Shell
|
1219
|
-
The input shell.
|
1220
|
-
angTolerance : float , optional
|
1221
|
-
The desired angular tolerance. The default is 0.1.
|
1222
|
-
tolerance : float , optional
|
1223
|
-
The desired tolerance. The default is 0.0001.
|
1224
|
-
|
1225
|
-
Returns
|
1226
|
-
-------
|
1227
|
-
topologic.Shell
|
1228
|
-
The created shell without any collinear edges.
|
1229
|
-
|
1230
|
-
"""
|
1231
|
-
from topologicpy.Face import Face
|
1232
|
-
|
1233
|
-
if not isinstance(shell, topologic.Shell):
|
1234
|
-
print("Shell.RemoveCollinearEdges - Error: The input shell parameter is not a valid shell. Returning None.")
|
1235
|
-
return None
|
1236
|
-
faces = Shell.Faces(shell)
|
1237
|
-
clean_faces = []
|
1238
|
-
for face in faces:
|
1239
|
-
clean_faces.append(Face.RemoveCollinearEdges(face, angTolerance=angTolerance, tolerance=tolerance))
|
1240
|
-
return Shell.ByFaces(clean_faces, tolerance=tolerance)
|
1241
|
-
|
1242
|
-
@staticmethod
|
1243
|
-
def Roof(face, angle: float = 45, epsilon: float = 0.01, tolerance: float = 0.001):
|
1244
|
-
"""
|
1245
|
-
Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1246
|
-
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1247
|
-
|
1248
|
-
Parameters
|
1249
|
-
----------
|
1250
|
-
face : topologic.Face
|
1251
|
-
The input face.
|
1252
|
-
angle : float , optioal
|
1253
|
-
The desired angle in degrees of the roof. The default is 45.
|
1254
|
-
epsilon : float , optional
|
1255
|
-
The desired epsilon (another form of tolerance for distance from plane). The default is 0.01. (This is set to a larger number as it was found to work better)
|
1256
|
-
tolerance : float , optional
|
1257
|
-
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
1258
|
-
|
1259
|
-
Returns
|
1260
|
-
-------
|
1261
|
-
topologic.Shell
|
1262
|
-
The created roof.
|
1263
|
-
|
1264
|
-
"""
|
1265
|
-
from topologicpy.Vertex import Vertex
|
1266
|
-
from topologicpy.Wire import Wire
|
1267
|
-
from topologicpy.Face import Face
|
1268
|
-
from topologicpy.Shell import Shell
|
1269
|
-
from topologicpy.Cell import Cell
|
1270
|
-
from topologicpy.Cluster import Cluster
|
1271
|
-
from topologicpy.Topology import Topology
|
1272
|
-
from topologicpy.Dictionary import Dictionary
|
1273
|
-
import topologic
|
1274
|
-
import math
|
1275
|
-
|
1276
|
-
def nearest_vertex_2d(v, vertices, tolerance=0.001):
|
1277
|
-
for vertex in vertices:
|
1278
|
-
x2 = Vertex.X(vertex)
|
1279
|
-
y2 = Vertex.Y(vertex)
|
1280
|
-
temp_v = Vertex.ByCoordinates(x2, y2, Vertex.Z(v))
|
1281
|
-
if Vertex.Distance(v, temp_v) <= tolerance:
|
1282
|
-
return vertex
|
1283
|
-
return None
|
1284
|
-
|
1285
|
-
if not isinstance(face, topologic.Face):
|
1286
|
-
return None
|
1287
|
-
angle = abs(angle)
|
1288
|
-
if angle >= 90-tolerance:
|
1289
|
-
return None
|
1290
|
-
if angle < tolerance:
|
1291
|
-
return None
|
1292
|
-
origin = Topology.Centroid(face)
|
1293
|
-
normal = Face.Normal(face)
|
1294
|
-
flat_face = Topology.Flatten(face, origin=origin, direction=normal)
|
1295
|
-
roof = Wire.Roof(flat_face, angle=angle, tolerance=tolerance)
|
1296
|
-
if not roof:
|
1297
|
-
return None
|
1298
|
-
shell = Shell.Skeleton(flat_face, tolerance=tolerance)
|
1299
|
-
faces = Shell.Faces(shell)
|
1300
|
-
Topology.Show(shell)
|
1301
|
-
if not faces:
|
1302
|
-
return None
|
1303
|
-
triangles = []
|
1304
|
-
for face in faces:
|
1305
|
-
internalBoundaries = Face.InternalBoundaries(face)
|
1306
|
-
if len(internalBoundaries) == 0:
|
1307
|
-
if len(Topology.Vertices(face)) > 3:
|
1308
|
-
triangles += Face.Triangulate(face, tolerance=tolerance)
|
1309
|
-
else:
|
1310
|
-
triangles += [face]
|
1311
|
-
|
1312
|
-
roof_vertices = Topology.Vertices(roof)
|
1313
|
-
flat_vertices = []
|
1314
|
-
for rv in roof_vertices:
|
1315
|
-
flat_vertices.append(Vertex.ByCoordinates(Vertex.X(rv), Vertex.Y(rv), 0))
|
1316
|
-
|
1317
|
-
final_triangles = []
|
1318
|
-
for triangle in triangles:
|
1319
|
-
if len(Topology.Vertices(triangle)) > 3:
|
1320
|
-
triangles = Face.Triangulate(triangle, tolerance=tolerance)
|
1321
|
-
else:
|
1322
|
-
triangles = [triangle]
|
1323
|
-
final_triangles += triangles
|
1324
|
-
|
1325
|
-
final_faces = []
|
1326
|
-
for triangle in final_triangles:
|
1327
|
-
face_vertices = Topology.Vertices(triangle)
|
1328
|
-
top_vertices = []
|
1329
|
-
for sv in face_vertices:
|
1330
|
-
temp = nearest_vertex_2d(sv, roof_vertices, tolerance=tolerance)
|
1331
|
-
if temp:
|
1332
|
-
top_vertices.append(temp)
|
1333
|
-
else:
|
1334
|
-
top_vertices.append(sv)
|
1335
|
-
tri_face = Face.ByVertices(top_vertices)
|
1336
|
-
final_faces.append(tri_face)
|
1337
|
-
|
1338
|
-
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1339
|
-
if not shell:
|
1340
|
-
shell = Cluster.ByTopologies(final_faces)
|
1341
|
-
try:
|
1342
|
-
shell = Topology.RemoveCoplanarFaces(shell, epsilon=epsilon, tolerance=tolerance)
|
1343
|
-
except:
|
1344
|
-
pass
|
1345
|
-
return shell
|
1346
|
-
|
1347
|
-
@staticmethod
|
1348
|
-
def SelfMerge(shell: topologic.Shell, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Face:
|
1349
|
-
"""
|
1350
|
-
Creates a face by merging the faces of the input shell. The shell must be planar within the input angular tolerance.
|
1351
|
-
|
1352
|
-
Parameters
|
1353
|
-
----------
|
1354
|
-
shell : topologic.Shell
|
1355
|
-
The input shell.
|
1356
|
-
angTolerance : float , optional
|
1357
|
-
The desired angular tolerance. The default is 0.1.
|
1358
|
-
tolerance : float , optional
|
1359
|
-
The desired tolerance. The default is 0.0001.
|
1360
|
-
|
1361
|
-
Returns
|
1362
|
-
-------
|
1363
|
-
topologic.Face
|
1364
|
-
The created face.
|
1365
|
-
|
1366
|
-
"""
|
1367
|
-
from topologicpy.Wire import Wire
|
1368
|
-
from topologicpy.Face import Face
|
1369
|
-
from topologicpy.Shell import Shell
|
1370
|
-
from topologicpy.Topology import Topology
|
1371
|
-
|
1372
|
-
def planarizeList(wireList):
|
1373
|
-
returnList = []
|
1374
|
-
for aWire in wireList:
|
1375
|
-
returnList.append(Wire.Planarize(aWire))
|
1376
|
-
return returnList
|
1377
|
-
if not isinstance(shell, topologic.Shell):
|
1378
|
-
return None
|
1379
|
-
ext_boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
|
1380
|
-
if isinstance(ext_boundary, topologic.Wire):
|
1381
|
-
f = Face.ByWire(Topology.RemoveCollinearEdges(ext_boundary, angTolerance), tolerance=tolerance) or Face.ByWire(Wire.Planarize(Topology.RemoveCollinearEdges(ext_boundary, angTolerance), tolerance=tolerance))
|
1382
|
-
if not f:
|
1383
|
-
print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
|
1384
|
-
return None
|
1385
|
-
else:
|
1386
|
-
return f
|
1387
|
-
elif isinstance(ext_boundary, topologic.Cluster):
|
1388
|
-
wires = []
|
1389
|
-
_ = ext_boundary.Wires(None, wires)
|
1390
|
-
faces = []
|
1391
|
-
areas = []
|
1392
|
-
for aWire in wires:
|
1393
|
-
try:
|
1394
|
-
aFace = topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(aWire, angTolerance))
|
1395
|
-
except:
|
1396
|
-
aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Topology.RemoveCollinearEdges(aWire, angTolerance)))
|
1397
|
-
anArea = Face.Area(aFace)
|
1398
|
-
faces.append(aFace)
|
1399
|
-
areas.append(anArea)
|
1400
|
-
max_index = areas.index(max(areas))
|
1401
|
-
ext_boundary = faces[max_index]
|
1402
|
-
int_boundaries = list(set(faces) - set([ext_boundary]))
|
1403
|
-
int_wires = []
|
1404
|
-
for int_boundary in int_boundaries:
|
1405
|
-
temp_wires = []
|
1406
|
-
_ = int_boundary.Wires(None, temp_wires)
|
1407
|
-
int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
|
1408
|
-
temp_wires = []
|
1409
|
-
_ = ext_boundary.Wires(None, temp_wires)
|
1410
|
-
ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
|
1411
|
-
try:
|
1412
|
-
return Face.ByWires(ext_wire, int_wires, tolerance=tolerance)
|
1413
|
-
except:
|
1414
|
-
return Face.ByWires(Wire.Planarize(ext_wire), planarizeList(int_wires), tolerance=tolerance)
|
1415
|
-
else:
|
1416
|
-
return None
|
1417
|
-
|
1418
|
-
def Skeleton(face, tolerance: float = 0.001):
|
1419
|
-
"""
|
1420
|
-
Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1421
|
-
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1422
|
-
|
1423
|
-
Parameters
|
1424
|
-
----------
|
1425
|
-
face : topologic.Face
|
1426
|
-
The input face.
|
1427
|
-
tolerance : float , optional
|
1428
|
-
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
1429
|
-
|
1430
|
-
Returns
|
1431
|
-
-------
|
1432
|
-
topologic.Shell
|
1433
|
-
The created straight skeleton.
|
1434
|
-
|
1435
|
-
"""
|
1436
|
-
from topologicpy.Wire import Wire
|
1437
|
-
from topologicpy.Face import Face
|
1438
|
-
from topologicpy.Topology import Topology
|
1439
|
-
import topologic
|
1440
|
-
import math
|
1441
|
-
|
1442
|
-
if not isinstance(face, topologic.Face):
|
1443
|
-
return None
|
1444
|
-
roof = Wire.Skeleton(face, tolerance=tolerance)
|
1445
|
-
if not roof:
|
1446
|
-
return None
|
1447
|
-
br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
|
1448
|
-
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
|
1449
|
-
bf = Face.ByWire(br, tolerance=tolerance)
|
1450
|
-
large_shell = Topology.Boolean(bf, roof, operation="slice", tolerance=tolerance)
|
1451
|
-
if not large_shell:
|
1452
|
-
return None
|
1453
|
-
faces = Topology.Faces(large_shell)
|
1454
|
-
if not faces:
|
1455
|
-
return None
|
1456
|
-
final_faces = []
|
1457
|
-
for f in faces:
|
1458
|
-
internalBoundaries = Face.InternalBoundaries(f)
|
1459
|
-
if len(internalBoundaries) == 0:
|
1460
|
-
final_faces.append(f)
|
1461
|
-
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1462
|
-
return shell
|
1463
|
-
|
1464
|
-
@staticmethod
|
1465
|
-
def Simplify(shell, simplifyBoundary=True, tolerance=0.0001):
|
1466
|
-
"""
|
1467
|
-
Simplifies the input shell edges based on the Douglas Peucker algorthim. See https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
|
1468
|
-
Part of this code was contributed by gaoxipeng. See https://github.com/wassimj/topologicpy/issues/35
|
1469
|
-
|
1470
|
-
Parameters
|
1471
|
-
----------
|
1472
|
-
shell : topologic.Shell
|
1473
|
-
The input shell.
|
1474
|
-
simplifyBoundary : bool , optional
|
1475
|
-
If set to True, the external boundary of the shell will be simplified as well. Otherwise, it will not be simplified. The default is True.
|
1476
|
-
tolerance : float , optional
|
1477
|
-
The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed.
|
1478
|
-
|
1479
|
-
Returns
|
1480
|
-
-------
|
1481
|
-
topologic.Shell
|
1482
|
-
The simplified shell.
|
1483
|
-
|
1484
|
-
"""
|
1485
|
-
from topologicpy.Vertex import Vertex
|
1486
|
-
from topologicpy.Wire import Wire
|
1487
|
-
from topologicpy.Face import Face
|
1488
|
-
from topologicpy.Shell import Shell
|
1489
|
-
from topologicpy.Cluster import Cluster
|
1490
|
-
from topologicpy.Topology import Topology
|
1491
|
-
from topologicpy.Helper import Helper
|
1492
|
-
|
1493
|
-
def perpendicular_distance(point, line_start, line_end):
|
1494
|
-
# Calculate the perpendicular distance from a point to a line segment
|
1495
|
-
x0 = point.X()
|
1496
|
-
y0 = point.Y()
|
1497
|
-
x1 = line_start.X()
|
1498
|
-
y1 = line_start.Y()
|
1499
|
-
x2 = line_end.X()
|
1500
|
-
y2 = line_end.Y()
|
1501
|
-
|
1502
|
-
numerator = abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1)
|
1503
|
-
denominator = Vertex.Distance(line_start, line_end)
|
1504
|
-
|
1505
|
-
return numerator / denominator
|
1506
|
-
|
1507
|
-
def douglas_peucker(wire, tolerance):
|
1508
|
-
if isinstance(wire, list):
|
1509
|
-
points = wire
|
1510
|
-
else:
|
1511
|
-
points = Wire.Vertices(wire)
|
1512
|
-
# points.insert(0, points.pop())
|
1513
|
-
if len(points) <= 2:
|
1514
|
-
return points
|
1515
|
-
|
1516
|
-
# Use the first and last points in the list as the starting and ending points
|
1517
|
-
start_point = points[0]
|
1518
|
-
end_point = points[-1]
|
1519
|
-
|
1520
|
-
# Find the point with the maximum distance
|
1521
|
-
max_distance = 0
|
1522
|
-
max_index = 0
|
1523
|
-
|
1524
|
-
for i in range(1, len(points) - 1):
|
1525
|
-
d = perpendicular_distance(points[i], start_point, end_point)
|
1526
|
-
if d > max_distance:
|
1527
|
-
max_distance = d
|
1528
|
-
max_index = i
|
1529
|
-
|
1530
|
-
# If the maximum distance is less than the tolerance, no further simplification is needed
|
1531
|
-
if max_distance <= tolerance:
|
1532
|
-
return [start_point, end_point]
|
1533
|
-
|
1534
|
-
# Recursively simplify
|
1535
|
-
first_segment = douglas_peucker(points[:max_index + 1], tolerance)
|
1536
|
-
second_segment = douglas_peucker(points[max_index:], tolerance)
|
1537
|
-
|
1538
|
-
# Merge the two simplified segments
|
1539
|
-
return first_segment[:-1] + second_segment
|
1540
|
-
if not isinstance(shell, topologic.Shell):
|
1541
|
-
print("Shell.Simplify - Error: The input shell parameter is not a valid topologic shell. Returning None.")
|
1542
|
-
return None
|
1543
|
-
# Get the external boundary of the shell. This can be simplified as well, but might cause issues at the end.
|
1544
|
-
# At this point, it is assumed to be left as is.
|
1545
|
-
all_edges = Topology.Edges(shell)
|
1546
|
-
if simplifyBoundary == False:
|
1547
|
-
ext_boundary = Face.ByWire(Shell.ExternalBoundary(shell, tolerance=tolerance), tolerance=tolerance)
|
1548
|
-
|
1549
|
-
# Get the internal edges of the shell.
|
1550
|
-
i_edges = []
|
1551
|
-
for edge in all_edges:
|
1552
|
-
faces = Topology.SuperTopologies(edge, shell, topologyType="face")
|
1553
|
-
if len(faces) > 1: # This means that the edge separates two faces so it is internal.
|
1554
|
-
i_edges.append(edge)
|
1555
|
-
# Creat a Wire from the internal edges
|
1556
|
-
wire = Topology.SelfMerge(Cluster.ByTopologies(i_edges), tolerance=tolerance)
|
1557
|
-
else:
|
1558
|
-
wire = Topology.SelfMerge(Cluster.ByTopologies(all_edges), tolerance=tolerance)
|
1559
|
-
# Split the wires at its junctions (where more than two edges meet at a vertex)
|
1560
|
-
components = Wire.Split(wire)
|
1561
|
-
separators = []
|
1562
|
-
wires = []
|
1563
|
-
for component in components:
|
1564
|
-
if isinstance(component, topologic.Cluster):
|
1565
|
-
component = Topology.SelfMerge(component, tolerance=tolerance)
|
1566
|
-
if isinstance(component, topologic.Cluster):
|
1567
|
-
separators.append(Cluster.FreeEdges(component, tolerance=tolerance))
|
1568
|
-
wires.append(Cluster.FreeWires(component, tolerance=tolerance))
|
1569
|
-
if isinstance(component, topologic.Edge):
|
1570
|
-
separators.append(component)
|
1571
|
-
if isinstance(component, topologic.Wire):
|
1572
|
-
wires.append(component)
|
1573
|
-
if isinstance(component, topologic.Edge):
|
1574
|
-
separators.append(component)
|
1575
|
-
if isinstance(component, topologic.Wire):
|
1576
|
-
wires.append(component)
|
1577
|
-
wires = Helper.Flatten(wires)
|
1578
|
-
separators = Helper.Flatten(separators)
|
1579
|
-
results = []
|
1580
|
-
for w in wires:
|
1581
|
-
temp_wire = Wire.ByVertices(douglas_peucker(w, tolerance), close=False)
|
1582
|
-
results.append(temp_wire)
|
1583
|
-
# Make a Cluster out of the results
|
1584
|
-
cluster = Cluster.ByTopologies(results)
|
1585
|
-
# Get all the edges of the result
|
1586
|
-
edges = Topology.Edges(cluster)
|
1587
|
-
# Add them to the final edges
|
1588
|
-
final_edges = edges + separators
|
1589
|
-
# Make a Cluster out of the final set of edges
|
1590
|
-
cluster = Cluster.ByTopologies(final_edges)
|
1591
|
-
if simplifyBoundary == False:
|
1592
|
-
# Slice the external boundary of the shell by the cluster
|
1593
|
-
final_result = Topology.Slice(ext_boundary, cluster, tolerance=tolerance)
|
1594
|
-
else:
|
1595
|
-
br = Wire.BoundingRectangle(shell)
|
1596
|
-
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1.5)
|
1597
|
-
br = Face.ByWire(br, tolerance=tolerance)
|
1598
|
-
v = Face.VertexByParameters(br, 0.1, 0.1)
|
1599
|
-
result = Topology.Slice(br, cluster, tolerance=tolerance)
|
1600
|
-
faces = Topology.Faces(result)
|
1601
|
-
final_faces = []
|
1602
|
-
for face in faces:
|
1603
|
-
if not Vertex.IsInternal(v, face, tolerance=0.01):
|
1604
|
-
final_faces.append(face)
|
1605
|
-
final_result = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1606
|
-
return final_result
|
1607
|
-
|
1608
|
-
|
1609
|
-
@staticmethod
|
1610
|
-
def Vertices(shell: topologic.Shell) -> list:
|
1611
|
-
"""
|
1612
|
-
Returns the vertices of the input shell.
|
1613
|
-
|
1614
|
-
Parameters
|
1615
|
-
----------
|
1616
|
-
shell : topologic.Shell
|
1617
|
-
The input shell.
|
1618
|
-
|
1619
|
-
Returns
|
1620
|
-
-------
|
1621
|
-
list
|
1622
|
-
The list of vertices.
|
1623
|
-
|
1624
|
-
"""
|
1625
|
-
if not isinstance(shell, topologic.Shell):
|
1626
|
-
return None
|
1627
|
-
vertices = []
|
1628
|
-
_ = shell.Vertices(None, vertices)
|
1629
|
-
return vertices
|
1630
|
-
|
1631
|
-
@staticmethod
|
1632
|
-
def Voronoi(vertices: list, face: topologic.Face = None, tolerance: float = 0.0001) -> topologic.Shell:
|
1633
|
-
"""
|
1634
|
-
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.
|
1635
|
-
|
1636
|
-
Parameters
|
1637
|
-
----------
|
1638
|
-
vertices : list
|
1639
|
-
The input list of vertices.
|
1640
|
-
face : topologic.Face , optional
|
1641
|
-
The input face. If the face is not set an optimised bounding rectangle of the input vertices is used instead. The default is None.
|
1642
|
-
tolerance : float , optional
|
1643
|
-
The desired tolerance. The default is 0.0001.
|
1644
|
-
|
1645
|
-
Returns
|
1646
|
-
-------
|
1647
|
-
shell
|
1648
|
-
A shell representing the voronoi partitioning of the input face.
|
1649
|
-
|
1650
|
-
"""
|
1651
|
-
from topologicpy.Vertex import Vertex
|
1652
|
-
from topologicpy.Edge import Edge
|
1653
|
-
from topologicpy.Wire import Wire
|
1654
|
-
from topologicpy.Face import Face
|
1655
|
-
from topologicpy.Cluster import Cluster
|
1656
|
-
from topologicpy.Topology import Topology
|
1657
|
-
from topologicpy.Dictionary import Dictionary
|
1658
|
-
|
1659
|
-
if not isinstance(face, topologic.Face):
|
1660
|
-
cluster = Cluster.ByTopologies(vertices)
|
1661
|
-
br = Wire.BoundingRectangle(cluster, optimize=5)
|
1662
|
-
face = Face.ByWire(br, tolerance=tolerance)
|
1663
|
-
if not isinstance(vertices, list):
|
1664
|
-
return None
|
1665
|
-
vertices = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
1666
|
-
if len(vertices) < 2:
|
1667
|
-
return None
|
1668
|
-
|
1669
|
-
# Flatten the input face
|
1670
|
-
origin = Topology.Centroid(face)
|
1671
|
-
normal = Face.Normal(face)
|
1672
|
-
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1673
|
-
eb = Face.ExternalBoundary(flatFace)
|
1674
|
-
ibList = Face.InternalBoundaries(flatFace)
|
1675
|
-
temp_verts = Topology.Vertices(eb)
|
1676
|
-
new_verts = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in temp_verts]
|
1677
|
-
eb = Wire.ByVertices(new_verts, close=True)
|
1678
|
-
new_ibList = []
|
1679
|
-
for ib in ibList:
|
1680
|
-
temp_verts = Topology.Vertices(ib)
|
1681
|
-
new_verts = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in temp_verts]
|
1682
|
-
new_ibList.append(Wire.ByVertices(new_verts, close=True))
|
1683
|
-
flatFace = Face.ByWires(eb, new_ibList)
|
1684
|
-
|
1685
|
-
# Create a cluster of the input vertices
|
1686
|
-
verticesCluster = Cluster.ByTopologies(vertices)
|
1687
|
-
|
1688
|
-
# Flatten the cluster using the same transformations
|
1689
|
-
verticesCluster = Topology.Flatten(verticesCluster, origin=origin, direction=normal)
|
1690
|
-
flatVertices = Topology.Vertices(verticesCluster)
|
1691
|
-
flatVertices = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in flatVertices]
|
1692
|
-
points = []
|
1693
|
-
for flatVertex in flatVertices:
|
1694
|
-
points.append([flatVertex.X(), flatVertex.Y()])
|
1695
|
-
|
1696
|
-
br = Wire.BoundingRectangle(flatFace)
|
1697
|
-
br_vertices = Wire.Vertices(br)
|
1698
|
-
br_x = []
|
1699
|
-
br_y = []
|
1700
|
-
for br_v in br_vertices:
|
1701
|
-
x, y = Vertex.Coordinates(br_v, outputType="xy")
|
1702
|
-
br_x.append(x)
|
1703
|
-
br_y.append(y)
|
1704
|
-
min_x = min(br_x)
|
1705
|
-
max_x = max(br_x)
|
1706
|
-
min_y = min(br_y)
|
1707
|
-
max_y = max(br_y)
|
1708
|
-
br_width = abs(max_x - min_x)
|
1709
|
-
br_length = abs(max_y - min_y)
|
1710
|
-
|
1711
|
-
points.append((-br_width*4, -br_length*4))
|
1712
|
-
points.append((-br_width*4, br_length*4))
|
1713
|
-
points.append((br_width*4, -br_length*4))
|
1714
|
-
points.append((br_width*4, br_length*4))
|
1715
|
-
|
1716
|
-
voronoi = Voronoi(points, furthest_site=False)
|
1717
|
-
voronoiVertices = []
|
1718
|
-
for v in voronoi.vertices:
|
1719
|
-
voronoiVertices.append(Vertex.ByCoordinates(v[0], v[1], 0))
|
1720
|
-
|
1721
|
-
faces = []
|
1722
|
-
for region in voronoi.regions:
|
1723
|
-
tempWire = []
|
1724
|
-
if len(region) > 1 and not -1 in region:
|
1725
|
-
for v in region:
|
1726
|
-
tempWire.append(Vertex.ByCoordinates(voronoiVertices[v].X(), voronoiVertices[v].Y(), 0))
|
1727
|
-
temp_verts = []
|
1728
|
-
for v in tempWire:
|
1729
|
-
if len(temp_verts) == 0:
|
1730
|
-
temp_verts.append(v)
|
1731
|
-
elif Vertex.Index(v, temp_verts) == None:
|
1732
|
-
temp_verts.append(v)
|
1733
|
-
tempWire = temp_verts
|
1734
|
-
temp_w = Wire.ByVertices(tempWire, close=True)
|
1735
|
-
faces.append(Face.ByWire(Wire.ByVertices(tempWire, close=True), tolerance=tolerance))
|
1736
|
-
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
1737
|
-
edges = Shell.Edges(shell)
|
1738
|
-
edgesCluster = Cluster.ByTopologies(edges)
|
1739
|
-
shell = Topology.Slice(flatFace,edgesCluster, tolerance=tolerance)
|
1740
|
-
shell = Topology.Unflatten(shell, origin=origin, direction=normal)
|
1741
|
-
return shell
|
1742
|
-
|
1743
|
-
@staticmethod
|
1744
|
-
def Wires(shell: topologic.Shell) -> list:
|
1745
|
-
"""
|
1746
|
-
Returns the wires of the input shell.
|
1747
|
-
|
1748
|
-
Parameters
|
1749
|
-
----------
|
1750
|
-
shell : topologic.Shell
|
1751
|
-
The input shell.
|
1752
|
-
|
1753
|
-
Returns
|
1754
|
-
-------
|
1755
|
-
list
|
1756
|
-
The list of wires.
|
1757
|
-
|
1758
|
-
"""
|
1759
|
-
if not isinstance(shell, topologic.Shell):
|
1760
|
-
return None
|
1761
|
-
wires = []
|
1762
|
-
_ = shell.Wires(None, wires)
|
1763
|
-
return wires
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1
|
+
# Copyright (C) 2024
|
2
|
+
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
import topologic_core as topologic
|
18
|
+
from topologicpy.Topology import Topology
|
19
|
+
import math
|
20
|
+
import os
|
21
|
+
import warnings
|
22
|
+
|
23
|
+
try:
|
24
|
+
from tqdm.auto import tqdm
|
25
|
+
except:
|
26
|
+
print("Shell - Installing required tqdm library.")
|
27
|
+
try:
|
28
|
+
os.system("pip install tqdm")
|
29
|
+
except:
|
30
|
+
os.system("pip install tqdm --user")
|
31
|
+
try:
|
32
|
+
from tqdm.auto import tqdm
|
33
|
+
print("Shell - tqdm library installed correctly.")
|
34
|
+
except:
|
35
|
+
warnings.warn("Shell - Error: Could not import tqdm.")
|
36
|
+
|
37
|
+
try:
|
38
|
+
from scipy.spatial import Delaunay
|
39
|
+
from scipy.spatial import Voronoi
|
40
|
+
except:
|
41
|
+
print("Shell - Install required scipy library.")
|
42
|
+
try:
|
43
|
+
os.system("pip install scipy")
|
44
|
+
except:
|
45
|
+
os.system("pip install scipy --user")
|
46
|
+
try:
|
47
|
+
from scipy.spatial import Delaunay
|
48
|
+
from scipy.spatial import Voronoi
|
49
|
+
except:
|
50
|
+
warnings.warn("Shell - Error: Could not import scipy.")
|
51
|
+
|
52
|
+
class Shell(Topology):
|
53
|
+
@staticmethod
|
54
|
+
def ByDisjointFaces(externalBoundary, faces, maximumGap=0.5, mergeJunctions=False, threshold=0.5, uSides=1, vSides=1, transferDictionaries=False, tolerance=0.0001):
|
55
|
+
"""
|
56
|
+
Creates a shell from an input list of disjointed faces. THIS IS STILL EXPERIMENTAL
|
57
|
+
|
58
|
+
Parameters
|
59
|
+
----------
|
60
|
+
externalBoundary : topologic.Face
|
61
|
+
The input external boundary of the faces. This resembles a ribbon (face with hole) where its interior boundary touches the edges of the input list of faces.
|
62
|
+
faces : list
|
63
|
+
The input list of faces.
|
64
|
+
maximumGap : float , optional
|
65
|
+
The length of the maximum gap between the faces. The default is 0.5.
|
66
|
+
mergeJunctions : bool , optional
|
67
|
+
If set to True, the interior junctions are merged into a single vertex. Otherwise, diagonal edges are added to resolve transitions between different gap distances.
|
68
|
+
threshold : float , optional
|
69
|
+
The desired threshold under which vertices are merged into a single vertex. The default is 0.5.
|
70
|
+
uSides : int , optional
|
71
|
+
The desired number of sides along the X axis for the grid that subdivides the input faces to aid in processing. The default is 1.
|
72
|
+
vSides : int , optional
|
73
|
+
The desired number of sides along the Y axis for the grid that subdivides the input faces to aid in processing. The default is 1.
|
74
|
+
transferDictionaries : bool, optional.
|
75
|
+
If set to True, the dictionaries in the input list of faces are transfered to the faces of the resulting shell. The default is False.
|
76
|
+
tolerance : float , optional
|
77
|
+
The desired tolerance. The default is 0.0001.
|
78
|
+
|
79
|
+
Returns
|
80
|
+
-------
|
81
|
+
topologic.Shell
|
82
|
+
The created Shell.
|
83
|
+
|
84
|
+
"""
|
85
|
+
from topologicpy.Vertex import Vertex
|
86
|
+
from topologicpy.Edge import Edge
|
87
|
+
from topologicpy.Wire import Wire
|
88
|
+
from topologicpy.Face import Face
|
89
|
+
from topologicpy.Cluster import Cluster
|
90
|
+
from topologicpy.Helper import Helper
|
91
|
+
from topologicpy.Topology import Topology
|
92
|
+
from topologicpy.Grid import Grid
|
93
|
+
from topologicpy.Dictionary import Dictionary
|
94
|
+
|
95
|
+
def removeShards(edges, hostTopology, maximumGap=0.5):
|
96
|
+
returnEdges = []
|
97
|
+
for e in tqdm(edges, desc="Removing Shards", leave=False):
|
98
|
+
if Edge.Length(e) < maximumGap:
|
99
|
+
sv = Edge.StartVertex(e)
|
100
|
+
ev = Edge.EndVertex(e)
|
101
|
+
sEdges = Topology.SuperTopologies(sv, hostTopology, "edge")
|
102
|
+
sn = len(sEdges)
|
103
|
+
eEdges = Topology.SuperTopologies(ev, hostTopology, "edge")
|
104
|
+
en = len(eEdges)
|
105
|
+
if sn >= 2 and en >= 2:
|
106
|
+
returnEdges.append(e)
|
107
|
+
else:
|
108
|
+
returnEdges.append(e)
|
109
|
+
return returnEdges
|
110
|
+
|
111
|
+
def extendEdges(edges, hostTopology, maximumGap=0.5):
|
112
|
+
returnEdges = []
|
113
|
+
for e in tqdm(edges, desc="Extending Edges", leave=False):
|
114
|
+
sv = Edge.StartVertex(e)
|
115
|
+
ev = Edge.EndVertex(e)
|
116
|
+
sEdges = Topology.SuperTopologies(sv, hostTopology, "edge")
|
117
|
+
sn = len(sEdges)
|
118
|
+
eEdges = Topology.SuperTopologies(ev, hostTopology, "edge")
|
119
|
+
en = len(eEdges)
|
120
|
+
if sn == 1:
|
121
|
+
ee = Edge.Extend(e, distance=maximumGap, bothSides=False, reverse=True)
|
122
|
+
returnEdges.append(ee)
|
123
|
+
elif en == 1:
|
124
|
+
ee = Edge.Extend(e, distance=maximumGap, bothSides=False, reverse=False)
|
125
|
+
returnEdges.append(ee)
|
126
|
+
else:
|
127
|
+
returnEdges.append(e)
|
128
|
+
return returnEdges
|
129
|
+
|
130
|
+
facesCluster = Cluster.ByTopologies(faces)
|
131
|
+
internalBoundary = Face.ByWire(Face.InternalBoundaries(externalBoundary)[0], tolerance=tolerance)
|
132
|
+
bb = Topology.BoundingBox(internalBoundary)
|
133
|
+
bb_d = Topology.Dictionary(bb)
|
134
|
+
unitU = Dictionary.ValueAtKey(bb_d, 'width') / uSides
|
135
|
+
unitV = Dictionary.ValueAtKey(bb_d, 'length') / vSides
|
136
|
+
uRange = [u*unitU for u in range(uSides)]
|
137
|
+
vRange = [v*unitV for v in range(vSides)]
|
138
|
+
grid = Grid.EdgesByDistances(internalBoundary, uRange=uRange, vRange=vRange, clip=True)
|
139
|
+
grid = Topology.Slice(internalBoundary, grid, tolerance=tolerance)
|
140
|
+
grid_faces = Topology.Faces(grid)
|
141
|
+
skeletons = []
|
142
|
+
for ib in tqdm(grid_faces, desc="Processing "+str(len(grid_faces))+" tiles", leave=False):
|
143
|
+
building_shell = Topology.Slice(ib, facesCluster, tolerance=tolerance)
|
144
|
+
wall_faces = Topology.Faces(building_shell)
|
145
|
+
walls = []
|
146
|
+
for w1 in wall_faces:
|
147
|
+
iv = Topology.InternalVertex(w1, tolerance=tolerance)
|
148
|
+
flag = False
|
149
|
+
for w2 in faces:
|
150
|
+
if Vertex.IsInternal(iv, w2):
|
151
|
+
flag = True
|
152
|
+
break;
|
153
|
+
if flag == False:
|
154
|
+
walls.append(w1)
|
155
|
+
for wall in walls:
|
156
|
+
skeleton = Wire.Skeleton(wall, tolerance=0.001) # This tolerance works better.
|
157
|
+
skeleton = Topology.Difference(skeleton, facesCluster, tolerance=tolerance)
|
158
|
+
skeleton = Topology.Difference(skeleton, Face.Wire(wall), tolerance=tolerance)
|
159
|
+
skeletons.append(skeleton)
|
160
|
+
if len(skeletons) > 0:
|
161
|
+
skeleton_cluster = Cluster.ByTopologies(skeletons+[internalBoundary])
|
162
|
+
skEdges = Topology.SelfMerge(Cluster.ByTopologies(removeShards(Topology.Edges(skeleton_cluster), skeleton_cluster, maximumGap=maximumGap)), tolerance=tolerance)
|
163
|
+
if isinstance(skEdges, topologic.Edge):
|
164
|
+
skEdges = extendEdges([skEdges], skEdges, maximumGap=maximumGap)
|
165
|
+
else:
|
166
|
+
skEdges = extendEdges(Topology.Edges(skEdges), skEdges, maximumGap=maximumGap)
|
167
|
+
if len(skEdges) < 1:
|
168
|
+
print("ShellByDisjointFaces - Warning: No edges were extended.")
|
169
|
+
#return Cluster.ByTopologies(skEdges)
|
170
|
+
#print("ShellByDisjointFaces - Error: Could not derive central skeleton of interior walls. Returning None.")
|
171
|
+
#return None
|
172
|
+
|
173
|
+
shell = Topology.Slice(internalBoundary, skeleton_cluster, tolerance=tolerance)
|
174
|
+
if mergeJunctions == True:
|
175
|
+
vertices = Shell.Vertices(shell)
|
176
|
+
centers = []
|
177
|
+
used = []
|
178
|
+
for v in vertices:
|
179
|
+
for w in vertices:
|
180
|
+
if not Topology.IsSame(v, w) and not w in used:
|
181
|
+
if Vertex.Distance(v, w) < threshold:
|
182
|
+
centers.append(v)
|
183
|
+
used.append(w)
|
184
|
+
edges = Shell.Edges(shell)
|
185
|
+
new_edges = []
|
186
|
+
for e in edges:
|
187
|
+
sv = Edge.StartVertex(e)
|
188
|
+
ev = Edge.EndVertex(e)
|
189
|
+
for v in centers:
|
190
|
+
if Vertex.Distance(sv, v) < threshold:
|
191
|
+
sv = v
|
192
|
+
if Vertex.Distance(ev, v) < threshold:
|
193
|
+
ev = v
|
194
|
+
new_edges.append(Edge.ByVertices([sv,ev], tolerance=tolerance))
|
195
|
+
cluster = Cluster.ByTopologies(new_edges)
|
196
|
+
|
197
|
+
vertices = Topology.Vertices(cluster)
|
198
|
+
edges = Topology.Edges(shell)
|
199
|
+
|
200
|
+
xList = list(set([Vertex.X(v) for v in vertices]))
|
201
|
+
xList.sort()
|
202
|
+
xList = Helper.MergeByThreshold(xList, 0.5)
|
203
|
+
yList = list(set([Vertex.Y(v) for v in vertices]))
|
204
|
+
yList.sort()
|
205
|
+
yList = Helper.MergeByThreshold(yList, 0.5)
|
206
|
+
yList.sort()
|
207
|
+
|
208
|
+
centers = []
|
209
|
+
|
210
|
+
new_edges = []
|
211
|
+
|
212
|
+
for e in edges:
|
213
|
+
sv = Edge.StartVertex(e)
|
214
|
+
ev = Edge.EndVertex(e)
|
215
|
+
svx = Vertex.X(sv)
|
216
|
+
svy = Vertex.Y(sv)
|
217
|
+
evx = Vertex.X(ev)
|
218
|
+
evy = Vertex.Y(ev)
|
219
|
+
for x in xList:
|
220
|
+
if abs(svx-x) < threshold:
|
221
|
+
svx = x
|
222
|
+
break;
|
223
|
+
for y in yList:
|
224
|
+
if abs(svy-y) < threshold:
|
225
|
+
svy = y
|
226
|
+
break;
|
227
|
+
sv = Vertex.ByCoordinates(svx, svy, 0)
|
228
|
+
for x in xList:
|
229
|
+
if abs(evx-x) < threshold:
|
230
|
+
evx = x
|
231
|
+
break;
|
232
|
+
for y in yList:
|
233
|
+
if abs(evy-y) < threshold:
|
234
|
+
evy = y
|
235
|
+
break;
|
236
|
+
sv = Vertex.ByCoordinates(svx, svy, 0)
|
237
|
+
ev = Vertex.ByCoordinates(evx, evy, 0)
|
238
|
+
new_edges.append(Edge.ByVertices([sv, ev], tolerance=tolerance))
|
239
|
+
|
240
|
+
cluster = Cluster.ByTopologies(new_edges)
|
241
|
+
eb = Face.ByWire(Shell.ExternalBoundary(shell), tolerance=tolerance)
|
242
|
+
shell = Topology.Slice(eb, cluster, tolerance=tolerance)
|
243
|
+
if not isinstance(shell, topologic.Shell):
|
244
|
+
try:
|
245
|
+
temp_wires = [Wire.RemoveCollinearEdges(w, angTolerance=1.0) for w in Topology.Wires(shell)]
|
246
|
+
temp_faces = [Face.ByWire(w, tolerance=tolerance) for w in temp_wires]
|
247
|
+
except:
|
248
|
+
temp_faces = Topology.Faces(shell)
|
249
|
+
shell = Shell.ByFaces(temp_faces, tolerance=tolerance)
|
250
|
+
if transferDictionaries == True:
|
251
|
+
selectors = []
|
252
|
+
for f in faces:
|
253
|
+
d = Topology.Dictionary(f)
|
254
|
+
s = Topology.InternalVertex(f, tolerance=tolerance)
|
255
|
+
s = Topology.SetDictionary(s, d)
|
256
|
+
selectors.append(s)
|
257
|
+
_ = Topology.TransferDictionariesBySelectors(topology=shell, selectors=selectors, tranFaces=True, tolerance=tolerance)
|
258
|
+
return shell
|
259
|
+
return None
|
260
|
+
|
261
|
+
@staticmethod
|
262
|
+
def ByFaces(faces: list, tolerance: float = 0.0001) -> topologic.Shell:
|
263
|
+
"""
|
264
|
+
Creates a shell from the input list of faces.
|
265
|
+
|
266
|
+
Parameters
|
267
|
+
----------
|
268
|
+
faces : list
|
269
|
+
The input list of faces.
|
270
|
+
tolerance : float , optional
|
271
|
+
The desired tolerance. The default is 0.0001.
|
272
|
+
|
273
|
+
Returns
|
274
|
+
-------
|
275
|
+
topologic.Shell
|
276
|
+
The created Shell.
|
277
|
+
|
278
|
+
"""
|
279
|
+
from topologicpy.Topology import Topology
|
280
|
+
|
281
|
+
if not isinstance(faces, list):
|
282
|
+
return None
|
283
|
+
faceList = [x for x in faces if isinstance(x, topologic.Face)]
|
284
|
+
if len(faceList) == 0:
|
285
|
+
print("Shell.ByFaces - Error: The input faces list does not contain any valid faces. Returning None.")
|
286
|
+
return None
|
287
|
+
shell = topologic.Shell.ByFaces(faceList, tolerance)
|
288
|
+
if not isinstance(shell, topologic.Shell):
|
289
|
+
shell = Topology.SelfMerge(shell, tolerance=tolerance)
|
290
|
+
if isinstance(shell, topologic.Shell):
|
291
|
+
return shell
|
292
|
+
else:
|
293
|
+
print("Shell.ByFaces - Error: Could not create shell. Returning None.")
|
294
|
+
return None
|
295
|
+
else:
|
296
|
+
return shell
|
297
|
+
|
298
|
+
@staticmethod
|
299
|
+
def ByFacesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Shell:
|
300
|
+
"""
|
301
|
+
Creates a shell from the input cluster of faces.
|
302
|
+
|
303
|
+
Parameters
|
304
|
+
----------
|
305
|
+
cluster : topologic.Cluster
|
306
|
+
The input cluster of faces.
|
307
|
+
tolerance : float , optional
|
308
|
+
The desired tolerance. The default is 0.0001.
|
309
|
+
|
310
|
+
Returns
|
311
|
+
-------
|
312
|
+
topologic.Shell
|
313
|
+
The created shell.
|
314
|
+
|
315
|
+
"""
|
316
|
+
if not isinstance(cluster, topologic.Cluster):
|
317
|
+
return None
|
318
|
+
faces = []
|
319
|
+
_ = cluster.Faces(None, faces)
|
320
|
+
return Shell.ByFaces(faces, tolerance=tolerance)
|
321
|
+
|
322
|
+
@staticmethod
|
323
|
+
def ByWires(wires: list, triangulate: bool = True, tolerance: float = 0.0001, silent: bool = False) -> topologic.Shell:
|
324
|
+
"""
|
325
|
+
Creates a shell by lofting through the input wires
|
326
|
+
Parameters
|
327
|
+
----------
|
328
|
+
wires : list
|
329
|
+
The input list of wires.
|
330
|
+
triangulate : bool , optional
|
331
|
+
If set to True, the faces will be triangulated. The default is True.
|
332
|
+
tolerance : float , optional
|
333
|
+
The desired tolerance. The default is 0.0001.
|
334
|
+
silent : bool , optional
|
335
|
+
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
|
336
|
+
|
337
|
+
Returns
|
338
|
+
-------
|
339
|
+
topologic.Shell
|
340
|
+
The creates shell.
|
341
|
+
"""
|
342
|
+
from topologicpy.Edge import Edge
|
343
|
+
from topologicpy.Wire import Wire
|
344
|
+
from topologicpy.Face import Face
|
345
|
+
if not isinstance(wires, list):
|
346
|
+
return None
|
347
|
+
wireList = [x for x in wires if isinstance(x, topologic.Wire)]
|
348
|
+
faces = []
|
349
|
+
for i in range(len(wireList)-1):
|
350
|
+
wire1 = wireList[i]
|
351
|
+
wire2 = wireList[i+1]
|
352
|
+
if wire1.Type() < topologic.Edge.Type() or wire2.Type() < topologic.Edge.Type():
|
353
|
+
return None
|
354
|
+
if wire1.Type() == topologic.Edge.Type():
|
355
|
+
w1_edges = [wire1]
|
356
|
+
else:
|
357
|
+
w1_edges = []
|
358
|
+
_ = wire1.Edges(None, w1_edges)
|
359
|
+
if wire2.Type() == topologic.Edge.Type():
|
360
|
+
w2_edges = [wire2]
|
361
|
+
else:
|
362
|
+
w2_edges = []
|
363
|
+
_ = wire2.Edges(None, w2_edges)
|
364
|
+
if len(w1_edges) != len(w2_edges):
|
365
|
+
return None
|
366
|
+
if triangulate == True:
|
367
|
+
for j in range (len(w1_edges)):
|
368
|
+
e1 = w1_edges[j]
|
369
|
+
e2 = w2_edges[j]
|
370
|
+
e3 = None
|
371
|
+
e4 = None
|
372
|
+
try:
|
373
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
374
|
+
except:
|
375
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
376
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e4], tolerance=tolerance), tolerance=tolerance))
|
377
|
+
try:
|
378
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
379
|
+
except:
|
380
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
381
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e2, e3],tolerance=tolerance), tolerance=tolerance))
|
382
|
+
if e3 and e4:
|
383
|
+
e5 = Edge.ByVertices([e1.StartVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
384
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e5, e4], tolerance=tolerance), tolerance=tolerance))
|
385
|
+
faces.append(Face.ByWire(Wire.ByEdges([e2, e5, e3], tolerance=tolerance), tolerance=tolerance))
|
386
|
+
else:
|
387
|
+
for j in range (len(w1_edges)):
|
388
|
+
e1 = w1_edges[j]
|
389
|
+
e2 = w2_edges[j]
|
390
|
+
e3 = None
|
391
|
+
e4 = None
|
392
|
+
try:
|
393
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
394
|
+
except:
|
395
|
+
try:
|
396
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
397
|
+
except:
|
398
|
+
pass
|
399
|
+
try:
|
400
|
+
e4 = Edge.ByVertices([e1.EndVertex(), e2.EndVertex()], tolerance=tolerance, silent=silent)
|
401
|
+
except:
|
402
|
+
try:
|
403
|
+
e3 = Edge.ByVertices([e1.StartVertex(), e2.StartVertex()], tolerance=tolerance, silent=silent)
|
404
|
+
except:
|
405
|
+
pass
|
406
|
+
if e3 and e4:
|
407
|
+
try:
|
408
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2, e3], tolerance=tolerance), tolerance=tolerance))
|
409
|
+
except:
|
410
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2, e4], tolerance=tolerance), tolerance=tolerance))
|
411
|
+
elif e3:
|
412
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e3, e2], tolerance=tolerance), tolerance=tolerance))
|
413
|
+
elif e4:
|
414
|
+
faces.append(Face.ByWire(Wire.ByEdges([e1, e4, e2], tolerance=tolerance), tolerance=tolerance))
|
415
|
+
return Shell.ByFaces(faces, tolerance=tolerance)
|
416
|
+
|
417
|
+
@staticmethod
|
418
|
+
def ByWiresCluster(cluster: topologic.Cluster, triangulate: bool = True, tolerance: float = 0.0001, silent: bool = False) -> topologic.Shell:
|
419
|
+
"""
|
420
|
+
Creates a shell by lofting through the input cluster of wires
|
421
|
+
|
422
|
+
Parameters
|
423
|
+
----------
|
424
|
+
wires : topologic.Cluster
|
425
|
+
The input cluster of wires.
|
426
|
+
triangulate : bool , optional
|
427
|
+
If set to True, the faces will be triangulated. The default is True.
|
428
|
+
tolerance : float , optional
|
429
|
+
The desired tolerance. The default is 0.0001.
|
430
|
+
silent : bool , optional
|
431
|
+
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
|
432
|
+
|
433
|
+
Returns
|
434
|
+
-------
|
435
|
+
topologic.Shell
|
436
|
+
The creates shell.
|
437
|
+
|
438
|
+
"""
|
439
|
+
from topologicpy.Cluster import Cluster
|
440
|
+
if not cluster:
|
441
|
+
return None
|
442
|
+
if not isinstance(cluster, topologic.Cluster):
|
443
|
+
return None
|
444
|
+
wires = Cluster.Wires(cluster)
|
445
|
+
return Shell.ByWires(wires, triangulate=triangulate, tolerance=tolerance, silent=silent)
|
446
|
+
|
447
|
+
@staticmethod
|
448
|
+
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:
|
449
|
+
"""
|
450
|
+
Creates a circle.
|
451
|
+
|
452
|
+
Parameters
|
453
|
+
----------
|
454
|
+
origin : topologic.Vertex , optional
|
455
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
456
|
+
radius : float , optional
|
457
|
+
The radius of the circle. The default is 0.5.
|
458
|
+
sides : int , optional
|
459
|
+
The number of sides of the circle. The default is 32.
|
460
|
+
fromAngle : float , optional
|
461
|
+
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
462
|
+
toAngle : float , optional
|
463
|
+
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
464
|
+
direction : list , optional
|
465
|
+
The vector representing the up direction of the circle. The default is [0, 0, 1].
|
466
|
+
placement : str , optional
|
467
|
+
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".
|
468
|
+
tolerance : float , optional
|
469
|
+
The desired tolerance. The default is 0.0001.
|
470
|
+
|
471
|
+
Returns
|
472
|
+
-------
|
473
|
+
topologic.Shell
|
474
|
+
The created circle.
|
475
|
+
"""
|
476
|
+
return Shell.Pie(origin=origin, radiusA=radius, radiusB=0, sides=sides, rings=1, fromAngle=fromAngle, toAngle=toAngle, direction=direction, placement=placement, tolerance=tolerance)
|
477
|
+
|
478
|
+
@staticmethod
|
479
|
+
def Delaunay(vertices: list, face: topologic.Face = None, tolerance: float = 0.0001) -> topologic.Shell:
|
480
|
+
"""
|
481
|
+
Returns a delaunay partitioning of the input vertices. The vertices must be coplanar. See https://en.wikipedia.org/wiki/Delaunay_triangulation.
|
482
|
+
|
483
|
+
Parameters
|
484
|
+
----------
|
485
|
+
vertices : list
|
486
|
+
The input list of vertices.
|
487
|
+
face : topologic.Face , optional
|
488
|
+
The input face. If specified, the delaunay triangulation is clipped to the face.
|
489
|
+
tolerance : float , optional
|
490
|
+
The desired tolerance. The default is 0.0001.
|
491
|
+
|
492
|
+
Returns
|
493
|
+
-------
|
494
|
+
shell
|
495
|
+
A shell representing the delaunay triangulation of the input vertices.
|
496
|
+
|
497
|
+
"""
|
498
|
+
from topologicpy.Vertex import Vertex
|
499
|
+
from topologicpy.Wire import Wire
|
500
|
+
from topologicpy.Face import Face
|
501
|
+
from topologicpy.Cluster import Cluster
|
502
|
+
from topologicpy.Topology import Topology
|
503
|
+
from topologicpy.Dictionary import Dictionary
|
504
|
+
from random import sample
|
505
|
+
from scipy.spatial import Delaunay as SCIDelaunay
|
506
|
+
import numpy as np
|
507
|
+
|
508
|
+
if not isinstance(vertices, list):
|
509
|
+
return None
|
510
|
+
vertices = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
511
|
+
if len(vertices) < 3:
|
512
|
+
return None
|
513
|
+
|
514
|
+
# Create a Vertex at the world's origin (0, 0, 0)
|
515
|
+
world_origin = Vertex.Origin()
|
516
|
+
|
517
|
+
if isinstance(face, topologic.Face):
|
518
|
+
# Flatten the face
|
519
|
+
origin = Topology.Centroid(face)
|
520
|
+
normal = Face.Normal(face)
|
521
|
+
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
522
|
+
faceVertices = Face.Vertices(face)
|
523
|
+
vertices += faceVertices
|
524
|
+
|
525
|
+
# Create a cluster of the input vertices
|
526
|
+
verticesCluster = Cluster.ByTopologies(vertices)
|
527
|
+
|
528
|
+
# Flatten the cluster using the same transformations
|
529
|
+
verticesCluster = Topology.Flatten(verticesCluster, origin=origin, direction=normal)
|
530
|
+
|
531
|
+
vertices = Cluster.Vertices(verticesCluster)
|
532
|
+
points = []
|
533
|
+
for v in vertices:
|
534
|
+
points.append([Vertex.X(v), Vertex.Y(v)])
|
535
|
+
delaunay = SCIDelaunay(points)
|
536
|
+
simplices = delaunay.simplices
|
537
|
+
|
538
|
+
faces = []
|
539
|
+
for simplex in simplices:
|
540
|
+
tempTriangleVertices = []
|
541
|
+
tempTriangleVertices.append(vertices[simplex[0]])
|
542
|
+
tempTriangleVertices.append(vertices[simplex[1]])
|
543
|
+
tempTriangleVertices.append(vertices[simplex[2]])
|
544
|
+
tempFace = Face.ByWire(Wire.ByVertices(tempTriangleVertices), tolerance=tolerance)
|
545
|
+
faces.append(tempFace)
|
546
|
+
|
547
|
+
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
548
|
+
if shell == None:
|
549
|
+
shell = Cluster.ByTopologies(faces)
|
550
|
+
|
551
|
+
if isinstance(face, topologic.Face):
|
552
|
+
edges = Topology.Edges(shell)
|
553
|
+
shell = Topology.Slice(flatFace, Cluster.ByTopologies(edges))
|
554
|
+
# Get the internal boundaries of the face
|
555
|
+
wires = Face.InternalBoundaries(flatFace)
|
556
|
+
ibList = []
|
557
|
+
if len(wires) > 0:
|
558
|
+
ibList = [Face.ByWire(w) for w in wires]
|
559
|
+
cluster = Cluster.ByTopologies(ibList)
|
560
|
+
shell = Topology.Difference(shell, cluster)
|
561
|
+
shell = Topology.Unflatten(shell, origin=origin, direction=normal)
|
562
|
+
return shell
|
563
|
+
|
564
|
+
@staticmethod
|
565
|
+
def Edges(shell: topologic.Shell) -> list:
|
566
|
+
"""
|
567
|
+
Returns the edges of the input shell.
|
568
|
+
|
569
|
+
Parameters
|
570
|
+
----------
|
571
|
+
shell : topologic.Shell
|
572
|
+
The input shell.
|
573
|
+
|
574
|
+
Returns
|
575
|
+
-------
|
576
|
+
list
|
577
|
+
The list of edges.
|
578
|
+
|
579
|
+
"""
|
580
|
+
if not isinstance(shell, topologic.Shell):
|
581
|
+
return None
|
582
|
+
edges = []
|
583
|
+
_ = shell.Edges(None, edges)
|
584
|
+
return edges
|
585
|
+
|
586
|
+
@staticmethod
|
587
|
+
def ExternalBoundary(shell: topologic.Shell, tolerance: float = 0.0001) -> topologic.Wire:
|
588
|
+
"""
|
589
|
+
Returns the external boundary of the input shell.
|
590
|
+
|
591
|
+
Parameters
|
592
|
+
----------
|
593
|
+
shell : topologic.Shell
|
594
|
+
The input shell.
|
595
|
+
tolerance : float , optional
|
596
|
+
The desired tolerance. The default is 0.0001.
|
597
|
+
|
598
|
+
Returns
|
599
|
+
-------
|
600
|
+
topologic.Wire or topologic.Cluster
|
601
|
+
The external boundary of the input shell. If the shell has holes, the return value will be a cluster of wires.
|
602
|
+
|
603
|
+
"""
|
604
|
+
from topologicpy.Wire import Wire
|
605
|
+
from topologicpy.Cluster import Cluster
|
606
|
+
from topologicpy.Topology import Topology
|
607
|
+
|
608
|
+
if not isinstance(shell, topologic.Shell):
|
609
|
+
return None
|
610
|
+
edges = []
|
611
|
+
_ = shell.Edges(None, edges)
|
612
|
+
obEdges = []
|
613
|
+
for anEdge in edges:
|
614
|
+
faces = []
|
615
|
+
_ = anEdge.Faces(shell, faces)
|
616
|
+
if len(faces) == 1:
|
617
|
+
obEdges.append(anEdge)
|
618
|
+
return Topology.SelfMerge(Cluster.ByTopologies(obEdges), tolerance=tolerance)
|
619
|
+
|
620
|
+
@staticmethod
|
621
|
+
def Faces(shell: topologic.Shell) -> list:
|
622
|
+
"""
|
623
|
+
Returns the faces of the input shell.
|
624
|
+
|
625
|
+
Parameters
|
626
|
+
----------
|
627
|
+
shell : topologic.Shell
|
628
|
+
The input shell.
|
629
|
+
|
630
|
+
Returns
|
631
|
+
-------
|
632
|
+
list
|
633
|
+
The list of faces.
|
634
|
+
|
635
|
+
"""
|
636
|
+
if not isinstance(shell, topologic.Shell):
|
637
|
+
return None
|
638
|
+
faces = []
|
639
|
+
_ = shell.Faces(None, faces)
|
640
|
+
return faces
|
641
|
+
|
642
|
+
@staticmethod
|
643
|
+
def IsOnBoundary(shell: topologic.Shell, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
644
|
+
"""
|
645
|
+
Returns True if the input vertex is on the boundary of the input shell. Returns False otherwise. On the boundary is defined as being on the boundary of one of the shell's external or internal boundaries
|
646
|
+
|
647
|
+
Parameters
|
648
|
+
----------
|
649
|
+
shell : topologic.Shell
|
650
|
+
The input shell.
|
651
|
+
vertex : topologic.Vertex
|
652
|
+
The input vertex.
|
653
|
+
tolerance : float , optional
|
654
|
+
The desired tolerance. The default is 0.0001.
|
655
|
+
|
656
|
+
Returns
|
657
|
+
-------
|
658
|
+
bool
|
659
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
660
|
+
|
661
|
+
"""
|
662
|
+
|
663
|
+
from topologicpy.Vertex import Vertex
|
664
|
+
|
665
|
+
if not isinstance(shell, topologic.Shell):
|
666
|
+
return None
|
667
|
+
if not isinstance(vertex, topologic.Vertex):
|
668
|
+
return None
|
669
|
+
boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
|
670
|
+
if Vertex.IsInternal(vertex, boundary, tolerance=tolerance):
|
671
|
+
return True
|
672
|
+
internal_boundaries = Shell.InternalBoundaries(shell, tolerance=tolerance)
|
673
|
+
for ib in internal_boundaries:
|
674
|
+
if Vertex.IsInternal(vertex, ib, tolerance=tolerance):
|
675
|
+
return True
|
676
|
+
return False
|
677
|
+
|
678
|
+
@staticmethod
|
679
|
+
def HyperbolicParaboloidRectangularDomain(origin: topologic.Vertex = None, llVertex: topologic.Vertex = None, lrVertex: topologic.Vertex =None, ulVertex: topologic.Vertex =None, urVertex: topologic.Vertex = None,
|
680
|
+
uSides: int = 10, vSides: int = 10, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
681
|
+
"""
|
682
|
+
Creates a hyperbolic paraboloid with a rectangular domain.
|
683
|
+
|
684
|
+
Parameters
|
685
|
+
----------
|
686
|
+
origin : topologic.Vertex , optional
|
687
|
+
The origin of the hyperbolic parabolid. If set to None, it will be placed at the (0, 0, 0) origin. The default is None.
|
688
|
+
llVertex : topologic.Vertex , optional
|
689
|
+
The lower left corner of the hyperbolic parabolid. If set to None, it will be set to (-0.5, -0.5, -0.5).
|
690
|
+
lrVertex : topologic.Vertex , optional
|
691
|
+
The lower right corner of the hyperbolic parabolid. If set to None, it will be set to (0.5, -0.5, 0.5).
|
692
|
+
ulVertex : topologic.Vertex , optional
|
693
|
+
The upper left corner of the hyperbolic parabolid. If set to None, it will be set to (-0.5, 0.5, 0.5).
|
694
|
+
urVertex : topologic.Vertex , optional
|
695
|
+
The upper right corner of the hyperbolic parabolid. If set to None, it will be set to (0.5, 0.5, -0.5).
|
696
|
+
uSides : int , optional
|
697
|
+
The number of segments along the X axis. The default is 10.
|
698
|
+
vSides : int , optional
|
699
|
+
The number of segments along the Y axis. The default is 10.
|
700
|
+
direction : list , optional
|
701
|
+
The vector representing the up direction of the hyperbolic parabolid. The default is [0, 0, 1].
|
702
|
+
placement : str , optional
|
703
|
+
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".
|
704
|
+
tolerance : float , optional
|
705
|
+
The desired tolerance. The default is 0.0001.
|
706
|
+
Returns
|
707
|
+
-------
|
708
|
+
topologic.Shell
|
709
|
+
The created hyperbolic paraboloid.
|
710
|
+
|
711
|
+
"""
|
712
|
+
from topologicpy.Vertex import Vertex
|
713
|
+
from topologicpy.Edge import Edge
|
714
|
+
from topologicpy.Face import Face
|
715
|
+
from topologicpy.Topology import Topology
|
716
|
+
if not isinstance(origin, topologic.Vertex):
|
717
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
718
|
+
if not isinstance(llVertex, topologic.Vertex):
|
719
|
+
llVertex = Vertex.ByCoordinates(-0.5, -0.5, -0.5)
|
720
|
+
if not isinstance(lrVertex, topologic.Vertex):
|
721
|
+
lrVertex = Vertex.ByCoordinates(0.5, -0.5, 0.5)
|
722
|
+
if not isinstance(ulVertex, topologic.Vertex):
|
723
|
+
ulVertex = Vertex.ByCoordinates(-0.5, 0.5, 0.5)
|
724
|
+
if not isinstance(urVertex, topologic.Vertex):
|
725
|
+
urVertex = Vertex.ByCoordinates(0.5, 0.5, -0.5)
|
726
|
+
e1 = Edge.ByVertices([llVertex, lrVertex], tolerance=tolerance)
|
727
|
+
e3 = Edge.ByVertices([urVertex, ulVertex], tolerance=tolerance)
|
728
|
+
edges = []
|
729
|
+
for i in range(uSides+1):
|
730
|
+
v1 = Edge.VertexByParameter(e1, float(i)/float(uSides))
|
731
|
+
v2 = Edge.VertexByParameter(e3, 1.0 - float(i)/float(uSides))
|
732
|
+
edges.append(Edge.ByVertices([v1, v2], tolerance=tolerance))
|
733
|
+
faces = []
|
734
|
+
for i in range(uSides):
|
735
|
+
for j in range(vSides):
|
736
|
+
v1 = Edge.VertexByParameter(edges[i], float(j)/float(vSides))
|
737
|
+
v2 = Edge.VertexByParameter(edges[i], float(j+1)/float(vSides))
|
738
|
+
v3 = Edge.VertexByParameter(edges[i+1], float(j+1)/float(vSides))
|
739
|
+
v4 = Edge.VertexByParameter(edges[i+1], float(j)/float(vSides))
|
740
|
+
faces.append(Face.ByVertices([v1, v2, v4]))
|
741
|
+
faces.append(Face.ByVertices([v4, v2, v3]))
|
742
|
+
returnTopology = Shell.ByFaces(faces, tolerance=tolerance)
|
743
|
+
if not returnTopology:
|
744
|
+
returnTopology = None
|
745
|
+
xOffset = 0
|
746
|
+
yOffset = 0
|
747
|
+
zOffset = 0
|
748
|
+
minX = min([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()])
|
749
|
+
maxX = max([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()])
|
750
|
+
minY = min([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()])
|
751
|
+
maxY = max([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()])
|
752
|
+
minZ = min([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()])
|
753
|
+
maxZ = max([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()])
|
754
|
+
if placement.lower() == "lowerleft":
|
755
|
+
xOffset = -minX
|
756
|
+
yOffset = -minY
|
757
|
+
zOffset = -minZ
|
758
|
+
elif placement.lower() == "bottom":
|
759
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
760
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
761
|
+
zOffset = -minZ
|
762
|
+
elif placement.lower() == "center":
|
763
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
764
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
765
|
+
zOffset = -(minZ + (maxZ - minZ)*0.5)
|
766
|
+
returnTopology = Topology.Translate(returnTopology, xOffset, yOffset, zOffset)
|
767
|
+
returnTopology = Topology.Place(returnTopology, originA=Vertex.Origin(), originB=origin)
|
768
|
+
returnTopology = Topology.Orient(returnTopology, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
769
|
+
return returnTopology
|
770
|
+
|
771
|
+
@staticmethod
|
772
|
+
def HyperbolicParaboloidCircularDomain(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 36, rings: int = 10,
|
773
|
+
A: float = 2.0, B: float = -2.0, direction: list = [0, 0, 1],
|
774
|
+
placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
775
|
+
"""
|
776
|
+
Creates a hyperbolic paraboloid with a circular domain. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
777
|
+
|
778
|
+
Parameters
|
779
|
+
----------
|
780
|
+
origin : topologic.Vertex , optional
|
781
|
+
The origin of the hyperbolic parabolid. If set to None, it will be placed at the (0, 0, 0) origin. The default is None.
|
782
|
+
radius : float , optional
|
783
|
+
The desired radius of the hyperbolic paraboloid. The default is 0.5.
|
784
|
+
sides : int , optional
|
785
|
+
The desired number of sides of the hyperbolic parabolid. The default is 36.
|
786
|
+
rings : int , optional
|
787
|
+
The desired number of concentric rings of the hyperbolic parabolid. The default is 10.
|
788
|
+
A : float , optional
|
789
|
+
The *A* constant in the equation z = A*x^2^ + B*y^2^. The default is 2.0.
|
790
|
+
B : float , optional
|
791
|
+
The *B* constant in the equation z = A*x^2^ + B*y^2^. The default is -2.0.
|
792
|
+
direction : list , optional
|
793
|
+
The vector representing the up direction of the hyperbolic paraboloid. The default is [0, 0, 1].
|
794
|
+
placement : str , optional
|
795
|
+
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".
|
796
|
+
tolerance : float , optional
|
797
|
+
The desired tolerance. The default is 0.0001.
|
798
|
+
Returns
|
799
|
+
-------
|
800
|
+
topologic.Shell
|
801
|
+
The created hyperboloic paraboloid.
|
802
|
+
|
803
|
+
"""
|
804
|
+
from topologicpy.Vertex import Vertex
|
805
|
+
from topologicpy.Face import Face
|
806
|
+
from topologicpy.Topology import Topology
|
807
|
+
if not isinstance(origin, topologic.Vertex):
|
808
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
809
|
+
uOffset = float(360)/float(sides)
|
810
|
+
vOffset = float(radius)/float(rings)
|
811
|
+
faces = []
|
812
|
+
for i in range(rings-1):
|
813
|
+
r1 = radius - vOffset*i
|
814
|
+
r2 = radius - vOffset*(i+1)
|
815
|
+
for j in range(sides-1):
|
816
|
+
a1 = math.radians(uOffset)*j
|
817
|
+
a2 = math.radians(uOffset)*(j+1)
|
818
|
+
x1 = math.sin(a1)*r1
|
819
|
+
y1 = math.cos(a1)*r1
|
820
|
+
z1 = A*x1*x1 + B*y1*y1
|
821
|
+
x2 = math.sin(a1)*r2
|
822
|
+
y2 = math.cos(a1)*r2
|
823
|
+
z2 = A*x2*x2 + B*y2*y2
|
824
|
+
x3 = math.sin(a2)*r2
|
825
|
+
y3 = math.cos(a2)*r2
|
826
|
+
z3 = A*x3*x3 + B*y3*y3
|
827
|
+
x4 = math.sin(a2)*r1
|
828
|
+
y4 = math.cos(a2)*r1
|
829
|
+
z4 = A*x4*x4 + B*y4*y4
|
830
|
+
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
831
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
832
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
833
|
+
v4 = topologic.Vertex.ByCoordinates(x4,y4,z4)
|
834
|
+
f1 = Face.ByVertices([v1,v2,v4])
|
835
|
+
f2 = Face.ByVertices([v4,v2,v3])
|
836
|
+
faces.append(f1)
|
837
|
+
faces.append(f2)
|
838
|
+
a1 = math.radians(uOffset)*(sides-1)
|
839
|
+
a2 = math.radians(360)
|
840
|
+
x1 = math.sin(a1)*r1
|
841
|
+
y1 = math.cos(a1)*r1
|
842
|
+
z1 = A*x1*x1 + B*y1*y1
|
843
|
+
x2 = math.sin(a1)*r2
|
844
|
+
y2 = math.cos(a1)*r2
|
845
|
+
z2 = A*x2*x2 + B*y2*y2
|
846
|
+
x3 = math.sin(a2)*r2
|
847
|
+
y3 = math.cos(a2)*r2
|
848
|
+
z3 = A*x3*x3 + B*y3*y3
|
849
|
+
x4 = math.sin(a2)*r1
|
850
|
+
y4 = math.cos(a2)*r1
|
851
|
+
z4 = A*x4*x4 + B*y4*y4
|
852
|
+
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
853
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
854
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
855
|
+
v4 = topologic.Vertex.ByCoordinates(x4,y4,z4)
|
856
|
+
f1 = Face.ByVertices([v1,v2,v4])
|
857
|
+
f2 = Face.ByVertices([v4,v2,v3])
|
858
|
+
faces.append(f1)
|
859
|
+
faces.append(f2)
|
860
|
+
# Special Case: Center triangles
|
861
|
+
r = vOffset
|
862
|
+
x1 = 0
|
863
|
+
y1 = 0
|
864
|
+
z1 = 0
|
865
|
+
v1 = topologic.Vertex.ByCoordinates(x1,y1,z1)
|
866
|
+
for j in range(sides-1):
|
867
|
+
a1 = math.radians(uOffset)*j
|
868
|
+
a2 = math.radians(uOffset)*(j+1)
|
869
|
+
x2 = math.sin(a1)*r
|
870
|
+
y2 = math.cos(a1)*r
|
871
|
+
z2 = A*x2*x2 + B*y2*y2
|
872
|
+
#z2 = 0
|
873
|
+
x3 = math.sin(a2)*r
|
874
|
+
y3 = math.cos(a2)*r
|
875
|
+
z3 = A*x3*x3 + B*y3*y3
|
876
|
+
#z3 = 0
|
877
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
878
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
879
|
+
f1 = Face.ByVertices([v2,v1,v3])
|
880
|
+
faces.append(f1)
|
881
|
+
a1 = math.radians(uOffset)*(sides-1)
|
882
|
+
a2 = math.radians(360)
|
883
|
+
x2 = math.sin(a1)*r
|
884
|
+
y2 = math.cos(a1)*r
|
885
|
+
z2 = A*x2*x2 + B*y2*y2
|
886
|
+
x3 = math.sin(a2)*r
|
887
|
+
y3 = math.cos(a2)*r
|
888
|
+
z3 = A*x3*x3 + B*y3*y3
|
889
|
+
v2 = topologic.Vertex.ByCoordinates(x2,y2,z2)
|
890
|
+
v3 = topologic.Vertex.ByCoordinates(x3,y3,z3)
|
891
|
+
f1 = Face.ByVertices([v2,v1,v3])
|
892
|
+
faces.append(f1)
|
893
|
+
returnTopology = topologic.Shell.ByFaces(faces, tolerance)
|
894
|
+
if not returnTopology:
|
895
|
+
returnTopology = topologic.Cluster.ByTopologies(faces)
|
896
|
+
vertices = []
|
897
|
+
_ = returnTopology.Vertices(None, vertices)
|
898
|
+
xList = []
|
899
|
+
yList = []
|
900
|
+
zList = []
|
901
|
+
for aVertex in vertices:
|
902
|
+
xList.append(aVertex.X())
|
903
|
+
yList.append(aVertex.Y())
|
904
|
+
zList.append(aVertex.Z())
|
905
|
+
minX = min(xList)
|
906
|
+
maxX = max(xList)
|
907
|
+
minY = min(yList)
|
908
|
+
maxY = max(yList)
|
909
|
+
minZ = min(zList)
|
910
|
+
maxZ = max(zList)
|
911
|
+
xOffset = 0
|
912
|
+
yOffset = 0
|
913
|
+
zOffset = 0
|
914
|
+
if placement.lower() == "lowerleft":
|
915
|
+
xOffset = -minX
|
916
|
+
yOffset = -minY
|
917
|
+
zOffset = -minZ
|
918
|
+
elif placement.lower() == "bottom":
|
919
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
920
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
921
|
+
zOffset = -minZ
|
922
|
+
elif placement.lower() == "center":
|
923
|
+
xOffset = -(minX + (maxX - minX)*0.5)
|
924
|
+
yOffset = -(minY + (maxY - minY)*0.5)
|
925
|
+
zOffset = -(minZ + (maxZ - minZ)*0.5)
|
926
|
+
returnTopology = Topology.Translate(returnTopology, xOffset, yOffset, zOffset)
|
927
|
+
returnTopology = Topology.Place(returnTopology, originA=Vertex.Origin(), originB=origin)
|
928
|
+
returnTopology = Topology.Orient(returnTopology, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
929
|
+
return returnTopology
|
930
|
+
|
931
|
+
@staticmethod
|
932
|
+
def InternalBoundaries(shell: topologic.Shell, tolerance=0.0001) -> topologic.Topology:
|
933
|
+
"""
|
934
|
+
Returns the internal boundaries (closed wires) of the input shell. Internal boundaries are considered holes.
|
935
|
+
|
936
|
+
Parameters
|
937
|
+
----------
|
938
|
+
shell : topologic.Shell
|
939
|
+
The input shell.
|
940
|
+
tolerance : float , optional
|
941
|
+
The desired tolerance. The default is 0.0001.
|
942
|
+
|
943
|
+
Returns
|
944
|
+
-------
|
945
|
+
list
|
946
|
+
The list of internal boundaries
|
947
|
+
|
948
|
+
"""
|
949
|
+
from topologicpy.Cluster import Cluster
|
950
|
+
from topologicpy.Topology import Topology
|
951
|
+
edges = []
|
952
|
+
_ = shell.Edges(None, edges)
|
953
|
+
ibEdges = []
|
954
|
+
for anEdge in edges:
|
955
|
+
faces = []
|
956
|
+
_ = anEdge.Faces(shell, faces)
|
957
|
+
if len(faces) > 1:
|
958
|
+
ibEdges.append(anEdge)
|
959
|
+
returnTopology = Topology.SelfMerge(Cluster.ByTopologies(ibEdges), tolerance=tolerance)
|
960
|
+
wires = Topology.Wires(returnTopology)
|
961
|
+
return wires
|
962
|
+
|
963
|
+
|
964
|
+
@staticmethod
|
965
|
+
def IsClosed(shell: topologic.Shell) -> bool:
|
966
|
+
"""
|
967
|
+
Returns True if the input shell is closed. Returns False otherwise.
|
968
|
+
|
969
|
+
Parameters
|
970
|
+
----------
|
971
|
+
shell : topologic.Shell
|
972
|
+
The input shell.
|
973
|
+
|
974
|
+
Returns
|
975
|
+
-------
|
976
|
+
bool
|
977
|
+
True if the input shell is closed. False otherwise.
|
978
|
+
|
979
|
+
"""
|
980
|
+
return shell.IsClosed()
|
981
|
+
|
982
|
+
@staticmethod
|
983
|
+
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:
|
984
|
+
"""
|
985
|
+
Creates a pie shape.
|
986
|
+
|
987
|
+
Parameters
|
988
|
+
----------
|
989
|
+
origin : topologic.Vertex , optional
|
990
|
+
The location of the origin of the pie. The default is None which results in the pie being placed at (0, 0, 0).
|
991
|
+
radiusA : float , optional
|
992
|
+
The outer radius of the pie. The default is 0.5.
|
993
|
+
radiusB : float , optional
|
994
|
+
The inner radius of the pie. The default is 0.25.
|
995
|
+
sides : int , optional
|
996
|
+
The number of sides of the pie. The default is 32.
|
997
|
+
rings : int , optional
|
998
|
+
The number of rings of the pie. The default is 1.
|
999
|
+
fromAngle : float , optional
|
1000
|
+
The angle in degrees from which to start creating the arc of the pie. The default is 0.
|
1001
|
+
toAngle : float , optional
|
1002
|
+
The angle in degrees at which to end creating the arc of the pie. The default is 360.
|
1003
|
+
direction : list , optional
|
1004
|
+
The vector representing the up direction of the pie. The default is [0, 0, 1].
|
1005
|
+
placement : str , optional
|
1006
|
+
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".
|
1007
|
+
tolerance : float , optional
|
1008
|
+
The desired tolerance. The default is 0.0001.
|
1009
|
+
|
1010
|
+
Returns
|
1011
|
+
-------
|
1012
|
+
topologic.Shell
|
1013
|
+
The created pie.
|
1014
|
+
|
1015
|
+
"""
|
1016
|
+
from topologicpy.Vertex import Vertex
|
1017
|
+
from topologicpy.Face import Face
|
1018
|
+
from topologicpy.Topology import Topology
|
1019
|
+
if not origin:
|
1020
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
1021
|
+
if not isinstance(origin, topologic.Vertex):
|
1022
|
+
return None
|
1023
|
+
if toAngle < fromAngle:
|
1024
|
+
toAngle += 360
|
1025
|
+
if abs(toAngle-fromAngle) < tolerance:
|
1026
|
+
return None
|
1027
|
+
fromAngle = math.radians(fromAngle)
|
1028
|
+
toAngle = math.radians(toAngle)
|
1029
|
+
angleRange = toAngle - fromAngle
|
1030
|
+
radiusA = abs(radiusA)
|
1031
|
+
radiusB = abs(radiusB)
|
1032
|
+
if radiusB > radiusA:
|
1033
|
+
temp = radiusA
|
1034
|
+
radiusA = radiusB
|
1035
|
+
radiusB = temp
|
1036
|
+
if abs(radiusA - radiusB) < tolerance or radiusA < tolerance:
|
1037
|
+
return None
|
1038
|
+
radiusRange = radiusA - radiusB
|
1039
|
+
sides = int(abs(math.floor(sides)))
|
1040
|
+
if sides < 3:
|
1041
|
+
return None
|
1042
|
+
rings = int(abs(rings))
|
1043
|
+
if radiusB < tolerance:
|
1044
|
+
radiusB = 0
|
1045
|
+
xOffset = 0
|
1046
|
+
yOffset = 0
|
1047
|
+
zOffset = 0
|
1048
|
+
if placement.lower() == "lowerleft":
|
1049
|
+
xOffset = radiusA
|
1050
|
+
yOffset = radiusA
|
1051
|
+
uOffset = float(angleRange)/float(sides)
|
1052
|
+
vOffset = float(radiusRange)/float(rings)
|
1053
|
+
faces = []
|
1054
|
+
if radiusB > tolerance:
|
1055
|
+
for i in range(rings):
|
1056
|
+
r1 = radiusA - vOffset*i
|
1057
|
+
r2 = radiusA - vOffset*(i+1)
|
1058
|
+
for j in range(sides):
|
1059
|
+
a1 = fromAngle + uOffset*j
|
1060
|
+
a2 = fromAngle + uOffset*(j+1)
|
1061
|
+
x1 = math.sin(a1)*r1
|
1062
|
+
y1 = math.cos(a1)*r1
|
1063
|
+
z1 = 0
|
1064
|
+
x2 = math.sin(a1)*r2
|
1065
|
+
y2 = math.cos(a1)*r2
|
1066
|
+
z2 = 0
|
1067
|
+
x3 = math.sin(a2)*r2
|
1068
|
+
y3 = math.cos(a2)*r2
|
1069
|
+
z3 = 0
|
1070
|
+
x4 = math.sin(a2)*r1
|
1071
|
+
y4 = math.cos(a2)*r1
|
1072
|
+
z4 = 0
|
1073
|
+
v1 = Vertex.ByCoordinates(x1,y1,z1)
|
1074
|
+
v2 = Vertex.ByCoordinates(x2,y2,z2)
|
1075
|
+
v3 = Vertex.ByCoordinates(x3,y3,z3)
|
1076
|
+
v4 = Vertex.ByCoordinates(x4,y4,z4)
|
1077
|
+
f1 = Face.ByVertices([v1,v2,v3,v4])
|
1078
|
+
faces.append(f1)
|
1079
|
+
else:
|
1080
|
+
x1 = 0
|
1081
|
+
y1 = 0
|
1082
|
+
z1 = 0
|
1083
|
+
v1 = Vertex.ByCoordinates(x1,y1,z1)
|
1084
|
+
for j in range(sides):
|
1085
|
+
a1 = fromAngle + uOffset*j
|
1086
|
+
a2 = fromAngle + uOffset*(j+1)
|
1087
|
+
x2 = math.sin(a1)*radiusA
|
1088
|
+
y2 = math.cos(a1)*radiusA
|
1089
|
+
z2 = 0
|
1090
|
+
x3 = math.sin(a2)*radiusA
|
1091
|
+
y3 = math.cos(a2)*radiusA
|
1092
|
+
z3 = 0
|
1093
|
+
v2 = Vertex.ByCoordinates(x2,y2,z2)
|
1094
|
+
v3 = Vertex.ByCoordinates(x3,y3,z3)
|
1095
|
+
f1 = Face.ByVertices([v2,v1,v3])
|
1096
|
+
faces.append(f1)
|
1097
|
+
|
1098
|
+
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
1099
|
+
if not shell:
|
1100
|
+
return None
|
1101
|
+
shell = Topology.Translate(shell, xOffset, yOffset, zOffset)
|
1102
|
+
shell = Topology.Place(shell, originA=Vertex.Origin(), originB=origin)
|
1103
|
+
shell = Topology.Orient(shell, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
1104
|
+
return shell
|
1105
|
+
|
1106
|
+
|
1107
|
+
@staticmethod
|
1108
|
+
def Planarize(shell: topologic.Shell, origin: topologic.Vertex = None, mantissa: int = 6, tolerance: float = 0.0001) -> topologic.Shell:
|
1109
|
+
"""
|
1110
|
+
Returns a planarized version of the input shell.
|
1111
|
+
|
1112
|
+
Parameters
|
1113
|
+
----------
|
1114
|
+
shell : topologic.Shell
|
1115
|
+
The input shell.
|
1116
|
+
tolerance : float, optional
|
1117
|
+
The desired tolerance. The default is 0.0001.
|
1118
|
+
origin : topologic.Vertex , optional
|
1119
|
+
The desired origin of the plane unto which the planar shell will be projected. If set to None, the centroid of the input shell will be chosen. The default is None.
|
1120
|
+
mantissa : int , optional
|
1121
|
+
The desired length of the mantissa. The default is 6.
|
1122
|
+
|
1123
|
+
Returns
|
1124
|
+
-------
|
1125
|
+
topologic.Shell
|
1126
|
+
The planarized shell.
|
1127
|
+
|
1128
|
+
"""
|
1129
|
+
from topologicpy.Vertex import Vertex
|
1130
|
+
from topologicpy.Face import Face
|
1131
|
+
from topologicpy.Cluster import Cluster
|
1132
|
+
from topologicpy.Topology import Topology
|
1133
|
+
|
1134
|
+
if not isinstance(shell, topologic.Shell):
|
1135
|
+
print("Shell.Planarize - Error: The input wire parameter is not a valid topologic shell. Returning None.")
|
1136
|
+
return None
|
1137
|
+
if origin == None:
|
1138
|
+
origin = Vertex.Origin()
|
1139
|
+
if not isinstance(origin, topologic.Vertex):
|
1140
|
+
print("Shell.Planarize - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
|
1141
|
+
return None
|
1142
|
+
|
1143
|
+
vertices = Topology.Vertices(shell)
|
1144
|
+
faces = Topology.Faces(shell)
|
1145
|
+
plane_equation = Vertex.PlaneEquation(vertices, mantissa=mantissa)
|
1146
|
+
rect = Face.RectangleByPlaneEquation(origin=origin , equation=plane_equation, tolerance=tolerance)
|
1147
|
+
new_vertices = [Vertex.Project(v, rect, mantissa=mantissa) for v in vertices]
|
1148
|
+
new_shell = Topology.ReplaceVertices(shell, verticesA=vertices, verticesB=new_vertices)
|
1149
|
+
new_faces = Topology.Faces(new_shell)
|
1150
|
+
return Topology.SelfMerge(Cluster.ByTopologies(new_faces), tolerance=tolerance)
|
1151
|
+
|
1152
|
+
@staticmethod
|
1153
|
+
def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0,
|
1154
|
+
uSides: int = 2, vSides: int = 2, direction: list = [0, 0, 1],
|
1155
|
+
placement: str = "center", tolerance: float = 0.0001) -> topologic.Shell:
|
1156
|
+
"""
|
1157
|
+
Creates a rectangle.
|
1158
|
+
|
1159
|
+
Parameters
|
1160
|
+
----------
|
1161
|
+
origin : topologic.Vertex , optional
|
1162
|
+
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0).
|
1163
|
+
width : float , optional
|
1164
|
+
The width of the rectangle. The default is 1.0.
|
1165
|
+
length : float , optional
|
1166
|
+
The length of the rectangle. The default is 1.0.
|
1167
|
+
uSides : int , optional
|
1168
|
+
The number of sides along the width. The default is 2.
|
1169
|
+
vSides : int , optional
|
1170
|
+
The number of sides along the length. The default is 2.
|
1171
|
+
direction : list , optional
|
1172
|
+
The vector representing the up direction of the rectangle. The default is [0, 0, 1].
|
1173
|
+
placement : str , optional
|
1174
|
+
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".
|
1175
|
+
tolerance : float , optional
|
1176
|
+
The desired tolerance. The default is 0.0001.
|
1177
|
+
|
1178
|
+
Returns
|
1179
|
+
-------
|
1180
|
+
topologic.Shell
|
1181
|
+
The created shell.
|
1182
|
+
|
1183
|
+
"""
|
1184
|
+
from topologicpy.Vertex import Vertex
|
1185
|
+
from topologicpy.Wire import Wire
|
1186
|
+
from topologicpy.Face import Face
|
1187
|
+
if not origin:
|
1188
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
1189
|
+
if not isinstance(origin, topologic.Vertex):
|
1190
|
+
return None
|
1191
|
+
uOffset = float(width)/float(uSides)
|
1192
|
+
vOffset = float(length)/float(vSides)
|
1193
|
+
faces = []
|
1194
|
+
if placement.lower() == "center":
|
1195
|
+
wOffset = width*0.5
|
1196
|
+
lOffset = length*0.5
|
1197
|
+
else:
|
1198
|
+
wOffset = 0
|
1199
|
+
lOffset = 0
|
1200
|
+
for i in range(uSides):
|
1201
|
+
for j in range(vSides):
|
1202
|
+
rOrigin = Vertex.ByCoordinates(i*uOffset - wOffset, j*vOffset - lOffset, 0)
|
1203
|
+
w = Wire.Rectangle(origin=rOrigin, width=uOffset, length=vOffset, direction=[0, 0, 1], placement="lowerleft", tolerance=tolerance)
|
1204
|
+
f = Face.ByWire(w, tolerance=tolerance)
|
1205
|
+
faces.append(f)
|
1206
|
+
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
1207
|
+
shell = Topology.Place(shell, originA=Vertex.Origin(), originB=origin)
|
1208
|
+
shell = Topology.Orient(shell, origin=origin, dirA=[0, 0, 1], dirB=direction)
|
1209
|
+
return shell
|
1210
|
+
|
1211
|
+
@staticmethod
|
1212
|
+
def RemoveCollinearEdges(shell: topologic.Shell, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
|
1213
|
+
"""
|
1214
|
+
Removes any collinear edges in the input shell.
|
1215
|
+
|
1216
|
+
Parameters
|
1217
|
+
----------
|
1218
|
+
shell : topologic.Shell
|
1219
|
+
The input shell.
|
1220
|
+
angTolerance : float , optional
|
1221
|
+
The desired angular tolerance. The default is 0.1.
|
1222
|
+
tolerance : float , optional
|
1223
|
+
The desired tolerance. The default is 0.0001.
|
1224
|
+
|
1225
|
+
Returns
|
1226
|
+
-------
|
1227
|
+
topologic.Shell
|
1228
|
+
The created shell without any collinear edges.
|
1229
|
+
|
1230
|
+
"""
|
1231
|
+
from topologicpy.Face import Face
|
1232
|
+
|
1233
|
+
if not isinstance(shell, topologic.Shell):
|
1234
|
+
print("Shell.RemoveCollinearEdges - Error: The input shell parameter is not a valid shell. Returning None.")
|
1235
|
+
return None
|
1236
|
+
faces = Shell.Faces(shell)
|
1237
|
+
clean_faces = []
|
1238
|
+
for face in faces:
|
1239
|
+
clean_faces.append(Face.RemoveCollinearEdges(face, angTolerance=angTolerance, tolerance=tolerance))
|
1240
|
+
return Shell.ByFaces(clean_faces, tolerance=tolerance)
|
1241
|
+
|
1242
|
+
@staticmethod
|
1243
|
+
def Roof(face, angle: float = 45, epsilon: float = 0.01, tolerance: float = 0.001):
|
1244
|
+
"""
|
1245
|
+
Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1246
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1247
|
+
|
1248
|
+
Parameters
|
1249
|
+
----------
|
1250
|
+
face : topologic.Face
|
1251
|
+
The input face.
|
1252
|
+
angle : float , optioal
|
1253
|
+
The desired angle in degrees of the roof. The default is 45.
|
1254
|
+
epsilon : float , optional
|
1255
|
+
The desired epsilon (another form of tolerance for distance from plane). The default is 0.01. (This is set to a larger number as it was found to work better)
|
1256
|
+
tolerance : float , optional
|
1257
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
1258
|
+
|
1259
|
+
Returns
|
1260
|
+
-------
|
1261
|
+
topologic.Shell
|
1262
|
+
The created roof.
|
1263
|
+
|
1264
|
+
"""
|
1265
|
+
from topologicpy.Vertex import Vertex
|
1266
|
+
from topologicpy.Wire import Wire
|
1267
|
+
from topologicpy.Face import Face
|
1268
|
+
from topologicpy.Shell import Shell
|
1269
|
+
from topologicpy.Cell import Cell
|
1270
|
+
from topologicpy.Cluster import Cluster
|
1271
|
+
from topologicpy.Topology import Topology
|
1272
|
+
from topologicpy.Dictionary import Dictionary
|
1273
|
+
import topologic_core as topologic
|
1274
|
+
import math
|
1275
|
+
|
1276
|
+
def nearest_vertex_2d(v, vertices, tolerance=0.001):
|
1277
|
+
for vertex in vertices:
|
1278
|
+
x2 = Vertex.X(vertex)
|
1279
|
+
y2 = Vertex.Y(vertex)
|
1280
|
+
temp_v = Vertex.ByCoordinates(x2, y2, Vertex.Z(v))
|
1281
|
+
if Vertex.Distance(v, temp_v) <= tolerance:
|
1282
|
+
return vertex
|
1283
|
+
return None
|
1284
|
+
|
1285
|
+
if not isinstance(face, topologic.Face):
|
1286
|
+
return None
|
1287
|
+
angle = abs(angle)
|
1288
|
+
if angle >= 90-tolerance:
|
1289
|
+
return None
|
1290
|
+
if angle < tolerance:
|
1291
|
+
return None
|
1292
|
+
origin = Topology.Centroid(face)
|
1293
|
+
normal = Face.Normal(face)
|
1294
|
+
flat_face = Topology.Flatten(face, origin=origin, direction=normal)
|
1295
|
+
roof = Wire.Roof(flat_face, angle=angle, tolerance=tolerance)
|
1296
|
+
if not roof:
|
1297
|
+
return None
|
1298
|
+
shell = Shell.Skeleton(flat_face, tolerance=tolerance)
|
1299
|
+
faces = Shell.Faces(shell)
|
1300
|
+
Topology.Show(shell)
|
1301
|
+
if not faces:
|
1302
|
+
return None
|
1303
|
+
triangles = []
|
1304
|
+
for face in faces:
|
1305
|
+
internalBoundaries = Face.InternalBoundaries(face)
|
1306
|
+
if len(internalBoundaries) == 0:
|
1307
|
+
if len(Topology.Vertices(face)) > 3:
|
1308
|
+
triangles += Face.Triangulate(face, tolerance=tolerance)
|
1309
|
+
else:
|
1310
|
+
triangles += [face]
|
1311
|
+
|
1312
|
+
roof_vertices = Topology.Vertices(roof)
|
1313
|
+
flat_vertices = []
|
1314
|
+
for rv in roof_vertices:
|
1315
|
+
flat_vertices.append(Vertex.ByCoordinates(Vertex.X(rv), Vertex.Y(rv), 0))
|
1316
|
+
|
1317
|
+
final_triangles = []
|
1318
|
+
for triangle in triangles:
|
1319
|
+
if len(Topology.Vertices(triangle)) > 3:
|
1320
|
+
triangles = Face.Triangulate(triangle, tolerance=tolerance)
|
1321
|
+
else:
|
1322
|
+
triangles = [triangle]
|
1323
|
+
final_triangles += triangles
|
1324
|
+
|
1325
|
+
final_faces = []
|
1326
|
+
for triangle in final_triangles:
|
1327
|
+
face_vertices = Topology.Vertices(triangle)
|
1328
|
+
top_vertices = []
|
1329
|
+
for sv in face_vertices:
|
1330
|
+
temp = nearest_vertex_2d(sv, roof_vertices, tolerance=tolerance)
|
1331
|
+
if temp:
|
1332
|
+
top_vertices.append(temp)
|
1333
|
+
else:
|
1334
|
+
top_vertices.append(sv)
|
1335
|
+
tri_face = Face.ByVertices(top_vertices)
|
1336
|
+
final_faces.append(tri_face)
|
1337
|
+
|
1338
|
+
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1339
|
+
if not shell:
|
1340
|
+
shell = Cluster.ByTopologies(final_faces)
|
1341
|
+
try:
|
1342
|
+
shell = Topology.RemoveCoplanarFaces(shell, epsilon=epsilon, tolerance=tolerance)
|
1343
|
+
except:
|
1344
|
+
pass
|
1345
|
+
return shell
|
1346
|
+
|
1347
|
+
@staticmethod
|
1348
|
+
def SelfMerge(shell: topologic.Shell, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Face:
|
1349
|
+
"""
|
1350
|
+
Creates a face by merging the faces of the input shell. The shell must be planar within the input angular tolerance.
|
1351
|
+
|
1352
|
+
Parameters
|
1353
|
+
----------
|
1354
|
+
shell : topologic.Shell
|
1355
|
+
The input shell.
|
1356
|
+
angTolerance : float , optional
|
1357
|
+
The desired angular tolerance. The default is 0.1.
|
1358
|
+
tolerance : float , optional
|
1359
|
+
The desired tolerance. The default is 0.0001.
|
1360
|
+
|
1361
|
+
Returns
|
1362
|
+
-------
|
1363
|
+
topologic.Face
|
1364
|
+
The created face.
|
1365
|
+
|
1366
|
+
"""
|
1367
|
+
from topologicpy.Wire import Wire
|
1368
|
+
from topologicpy.Face import Face
|
1369
|
+
from topologicpy.Shell import Shell
|
1370
|
+
from topologicpy.Topology import Topology
|
1371
|
+
|
1372
|
+
def planarizeList(wireList):
|
1373
|
+
returnList = []
|
1374
|
+
for aWire in wireList:
|
1375
|
+
returnList.append(Wire.Planarize(aWire))
|
1376
|
+
return returnList
|
1377
|
+
if not isinstance(shell, topologic.Shell):
|
1378
|
+
return None
|
1379
|
+
ext_boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
|
1380
|
+
if isinstance(ext_boundary, topologic.Wire):
|
1381
|
+
f = Face.ByWire(Topology.RemoveCollinearEdges(ext_boundary, angTolerance), tolerance=tolerance) or Face.ByWire(Wire.Planarize(Topology.RemoveCollinearEdges(ext_boundary, angTolerance), tolerance=tolerance))
|
1382
|
+
if not f:
|
1383
|
+
print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
|
1384
|
+
return None
|
1385
|
+
else:
|
1386
|
+
return f
|
1387
|
+
elif isinstance(ext_boundary, topologic.Cluster):
|
1388
|
+
wires = []
|
1389
|
+
_ = ext_boundary.Wires(None, wires)
|
1390
|
+
faces = []
|
1391
|
+
areas = []
|
1392
|
+
for aWire in wires:
|
1393
|
+
try:
|
1394
|
+
aFace = topologic.Face.ByExternalBoundary(Topology.RemoveCollinearEdges(aWire, angTolerance))
|
1395
|
+
except:
|
1396
|
+
aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Topology.RemoveCollinearEdges(aWire, angTolerance)))
|
1397
|
+
anArea = Face.Area(aFace)
|
1398
|
+
faces.append(aFace)
|
1399
|
+
areas.append(anArea)
|
1400
|
+
max_index = areas.index(max(areas))
|
1401
|
+
ext_boundary = faces[max_index]
|
1402
|
+
int_boundaries = list(set(faces) - set([ext_boundary]))
|
1403
|
+
int_wires = []
|
1404
|
+
for int_boundary in int_boundaries:
|
1405
|
+
temp_wires = []
|
1406
|
+
_ = int_boundary.Wires(None, temp_wires)
|
1407
|
+
int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
|
1408
|
+
temp_wires = []
|
1409
|
+
_ = ext_boundary.Wires(None, temp_wires)
|
1410
|
+
ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
|
1411
|
+
try:
|
1412
|
+
return Face.ByWires(ext_wire, int_wires, tolerance=tolerance)
|
1413
|
+
except:
|
1414
|
+
return Face.ByWires(Wire.Planarize(ext_wire), planarizeList(int_wires), tolerance=tolerance)
|
1415
|
+
else:
|
1416
|
+
return None
|
1417
|
+
|
1418
|
+
def Skeleton(face, tolerance: float = 0.001):
|
1419
|
+
"""
|
1420
|
+
Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1421
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1422
|
+
|
1423
|
+
Parameters
|
1424
|
+
----------
|
1425
|
+
face : topologic.Face
|
1426
|
+
The input face.
|
1427
|
+
tolerance : float , optional
|
1428
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
1429
|
+
|
1430
|
+
Returns
|
1431
|
+
-------
|
1432
|
+
topologic.Shell
|
1433
|
+
The created straight skeleton.
|
1434
|
+
|
1435
|
+
"""
|
1436
|
+
from topologicpy.Wire import Wire
|
1437
|
+
from topologicpy.Face import Face
|
1438
|
+
from topologicpy.Topology import Topology
|
1439
|
+
import topologic_core as topologic
|
1440
|
+
import math
|
1441
|
+
|
1442
|
+
if not isinstance(face, topologic.Face):
|
1443
|
+
return None
|
1444
|
+
roof = Wire.Skeleton(face, tolerance=tolerance)
|
1445
|
+
if not roof:
|
1446
|
+
return None
|
1447
|
+
br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
|
1448
|
+
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
|
1449
|
+
bf = Face.ByWire(br, tolerance=tolerance)
|
1450
|
+
large_shell = Topology.Boolean(bf, roof, operation="slice", tolerance=tolerance)
|
1451
|
+
if not large_shell:
|
1452
|
+
return None
|
1453
|
+
faces = Topology.Faces(large_shell)
|
1454
|
+
if not faces:
|
1455
|
+
return None
|
1456
|
+
final_faces = []
|
1457
|
+
for f in faces:
|
1458
|
+
internalBoundaries = Face.InternalBoundaries(f)
|
1459
|
+
if len(internalBoundaries) == 0:
|
1460
|
+
final_faces.append(f)
|
1461
|
+
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1462
|
+
return shell
|
1463
|
+
|
1464
|
+
@staticmethod
|
1465
|
+
def Simplify(shell, simplifyBoundary=True, tolerance=0.0001):
|
1466
|
+
"""
|
1467
|
+
Simplifies the input shell edges based on the Douglas Peucker algorthim. See https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
|
1468
|
+
Part of this code was contributed by gaoxipeng. See https://github.com/wassimj/topologicpy/issues/35
|
1469
|
+
|
1470
|
+
Parameters
|
1471
|
+
----------
|
1472
|
+
shell : topologic.Shell
|
1473
|
+
The input shell.
|
1474
|
+
simplifyBoundary : bool , optional
|
1475
|
+
If set to True, the external boundary of the shell will be simplified as well. Otherwise, it will not be simplified. The default is True.
|
1476
|
+
tolerance : float , optional
|
1477
|
+
The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed.
|
1478
|
+
|
1479
|
+
Returns
|
1480
|
+
-------
|
1481
|
+
topologic.Shell
|
1482
|
+
The simplified shell.
|
1483
|
+
|
1484
|
+
"""
|
1485
|
+
from topologicpy.Vertex import Vertex
|
1486
|
+
from topologicpy.Wire import Wire
|
1487
|
+
from topologicpy.Face import Face
|
1488
|
+
from topologicpy.Shell import Shell
|
1489
|
+
from topologicpy.Cluster import Cluster
|
1490
|
+
from topologicpy.Topology import Topology
|
1491
|
+
from topologicpy.Helper import Helper
|
1492
|
+
|
1493
|
+
def perpendicular_distance(point, line_start, line_end):
|
1494
|
+
# Calculate the perpendicular distance from a point to a line segment
|
1495
|
+
x0 = point.X()
|
1496
|
+
y0 = point.Y()
|
1497
|
+
x1 = line_start.X()
|
1498
|
+
y1 = line_start.Y()
|
1499
|
+
x2 = line_end.X()
|
1500
|
+
y2 = line_end.Y()
|
1501
|
+
|
1502
|
+
numerator = abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1)
|
1503
|
+
denominator = Vertex.Distance(line_start, line_end)
|
1504
|
+
|
1505
|
+
return numerator / denominator
|
1506
|
+
|
1507
|
+
def douglas_peucker(wire, tolerance):
|
1508
|
+
if isinstance(wire, list):
|
1509
|
+
points = wire
|
1510
|
+
else:
|
1511
|
+
points = Wire.Vertices(wire)
|
1512
|
+
# points.insert(0, points.pop())
|
1513
|
+
if len(points) <= 2:
|
1514
|
+
return points
|
1515
|
+
|
1516
|
+
# Use the first and last points in the list as the starting and ending points
|
1517
|
+
start_point = points[0]
|
1518
|
+
end_point = points[-1]
|
1519
|
+
|
1520
|
+
# Find the point with the maximum distance
|
1521
|
+
max_distance = 0
|
1522
|
+
max_index = 0
|
1523
|
+
|
1524
|
+
for i in range(1, len(points) - 1):
|
1525
|
+
d = perpendicular_distance(points[i], start_point, end_point)
|
1526
|
+
if d > max_distance:
|
1527
|
+
max_distance = d
|
1528
|
+
max_index = i
|
1529
|
+
|
1530
|
+
# If the maximum distance is less than the tolerance, no further simplification is needed
|
1531
|
+
if max_distance <= tolerance:
|
1532
|
+
return [start_point, end_point]
|
1533
|
+
|
1534
|
+
# Recursively simplify
|
1535
|
+
first_segment = douglas_peucker(points[:max_index + 1], tolerance)
|
1536
|
+
second_segment = douglas_peucker(points[max_index:], tolerance)
|
1537
|
+
|
1538
|
+
# Merge the two simplified segments
|
1539
|
+
return first_segment[:-1] + second_segment
|
1540
|
+
if not isinstance(shell, topologic.Shell):
|
1541
|
+
print("Shell.Simplify - Error: The input shell parameter is not a valid topologic shell. Returning None.")
|
1542
|
+
return None
|
1543
|
+
# Get the external boundary of the shell. This can be simplified as well, but might cause issues at the end.
|
1544
|
+
# At this point, it is assumed to be left as is.
|
1545
|
+
all_edges = Topology.Edges(shell)
|
1546
|
+
if simplifyBoundary == False:
|
1547
|
+
ext_boundary = Face.ByWire(Shell.ExternalBoundary(shell, tolerance=tolerance), tolerance=tolerance)
|
1548
|
+
|
1549
|
+
# Get the internal edges of the shell.
|
1550
|
+
i_edges = []
|
1551
|
+
for edge in all_edges:
|
1552
|
+
faces = Topology.SuperTopologies(edge, shell, topologyType="face")
|
1553
|
+
if len(faces) > 1: # This means that the edge separates two faces so it is internal.
|
1554
|
+
i_edges.append(edge)
|
1555
|
+
# Creat a Wire from the internal edges
|
1556
|
+
wire = Topology.SelfMerge(Cluster.ByTopologies(i_edges), tolerance=tolerance)
|
1557
|
+
else:
|
1558
|
+
wire = Topology.SelfMerge(Cluster.ByTopologies(all_edges), tolerance=tolerance)
|
1559
|
+
# Split the wires at its junctions (where more than two edges meet at a vertex)
|
1560
|
+
components = Wire.Split(wire)
|
1561
|
+
separators = []
|
1562
|
+
wires = []
|
1563
|
+
for component in components:
|
1564
|
+
if isinstance(component, topologic.Cluster):
|
1565
|
+
component = Topology.SelfMerge(component, tolerance=tolerance)
|
1566
|
+
if isinstance(component, topologic.Cluster):
|
1567
|
+
separators.append(Cluster.FreeEdges(component, tolerance=tolerance))
|
1568
|
+
wires.append(Cluster.FreeWires(component, tolerance=tolerance))
|
1569
|
+
if isinstance(component, topologic.Edge):
|
1570
|
+
separators.append(component)
|
1571
|
+
if isinstance(component, topologic.Wire):
|
1572
|
+
wires.append(component)
|
1573
|
+
if isinstance(component, topologic.Edge):
|
1574
|
+
separators.append(component)
|
1575
|
+
if isinstance(component, topologic.Wire):
|
1576
|
+
wires.append(component)
|
1577
|
+
wires = Helper.Flatten(wires)
|
1578
|
+
separators = Helper.Flatten(separators)
|
1579
|
+
results = []
|
1580
|
+
for w in wires:
|
1581
|
+
temp_wire = Wire.ByVertices(douglas_peucker(w, tolerance), close=False)
|
1582
|
+
results.append(temp_wire)
|
1583
|
+
# Make a Cluster out of the results
|
1584
|
+
cluster = Cluster.ByTopologies(results)
|
1585
|
+
# Get all the edges of the result
|
1586
|
+
edges = Topology.Edges(cluster)
|
1587
|
+
# Add them to the final edges
|
1588
|
+
final_edges = edges + separators
|
1589
|
+
# Make a Cluster out of the final set of edges
|
1590
|
+
cluster = Cluster.ByTopologies(final_edges)
|
1591
|
+
if simplifyBoundary == False:
|
1592
|
+
# Slice the external boundary of the shell by the cluster
|
1593
|
+
final_result = Topology.Slice(ext_boundary, cluster, tolerance=tolerance)
|
1594
|
+
else:
|
1595
|
+
br = Wire.BoundingRectangle(shell)
|
1596
|
+
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1.5)
|
1597
|
+
br = Face.ByWire(br, tolerance=tolerance)
|
1598
|
+
v = Face.VertexByParameters(br, 0.1, 0.1)
|
1599
|
+
result = Topology.Slice(br, cluster, tolerance=tolerance)
|
1600
|
+
faces = Topology.Faces(result)
|
1601
|
+
final_faces = []
|
1602
|
+
for face in faces:
|
1603
|
+
if not Vertex.IsInternal(v, face, tolerance=0.01):
|
1604
|
+
final_faces.append(face)
|
1605
|
+
final_result = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1606
|
+
return final_result
|
1607
|
+
|
1608
|
+
|
1609
|
+
@staticmethod
|
1610
|
+
def Vertices(shell: topologic.Shell) -> list:
|
1611
|
+
"""
|
1612
|
+
Returns the vertices of the input shell.
|
1613
|
+
|
1614
|
+
Parameters
|
1615
|
+
----------
|
1616
|
+
shell : topologic.Shell
|
1617
|
+
The input shell.
|
1618
|
+
|
1619
|
+
Returns
|
1620
|
+
-------
|
1621
|
+
list
|
1622
|
+
The list of vertices.
|
1623
|
+
|
1624
|
+
"""
|
1625
|
+
if not isinstance(shell, topologic.Shell):
|
1626
|
+
return None
|
1627
|
+
vertices = []
|
1628
|
+
_ = shell.Vertices(None, vertices)
|
1629
|
+
return vertices
|
1630
|
+
|
1631
|
+
@staticmethod
|
1632
|
+
def Voronoi(vertices: list, face: topologic.Face = None, tolerance: float = 0.0001) -> topologic.Shell:
|
1633
|
+
"""
|
1634
|
+
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.
|
1635
|
+
|
1636
|
+
Parameters
|
1637
|
+
----------
|
1638
|
+
vertices : list
|
1639
|
+
The input list of vertices.
|
1640
|
+
face : topologic.Face , optional
|
1641
|
+
The input face. If the face is not set an optimised bounding rectangle of the input vertices is used instead. The default is None.
|
1642
|
+
tolerance : float , optional
|
1643
|
+
The desired tolerance. The default is 0.0001.
|
1644
|
+
|
1645
|
+
Returns
|
1646
|
+
-------
|
1647
|
+
shell
|
1648
|
+
A shell representing the voronoi partitioning of the input face.
|
1649
|
+
|
1650
|
+
"""
|
1651
|
+
from topologicpy.Vertex import Vertex
|
1652
|
+
from topologicpy.Edge import Edge
|
1653
|
+
from topologicpy.Wire import Wire
|
1654
|
+
from topologicpy.Face import Face
|
1655
|
+
from topologicpy.Cluster import Cluster
|
1656
|
+
from topologicpy.Topology import Topology
|
1657
|
+
from topologicpy.Dictionary import Dictionary
|
1658
|
+
|
1659
|
+
if not isinstance(face, topologic.Face):
|
1660
|
+
cluster = Cluster.ByTopologies(vertices)
|
1661
|
+
br = Wire.BoundingRectangle(cluster, optimize=5)
|
1662
|
+
face = Face.ByWire(br, tolerance=tolerance)
|
1663
|
+
if not isinstance(vertices, list):
|
1664
|
+
return None
|
1665
|
+
vertices = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
1666
|
+
if len(vertices) < 2:
|
1667
|
+
return None
|
1668
|
+
|
1669
|
+
# Flatten the input face
|
1670
|
+
origin = Topology.Centroid(face)
|
1671
|
+
normal = Face.Normal(face)
|
1672
|
+
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1673
|
+
eb = Face.ExternalBoundary(flatFace)
|
1674
|
+
ibList = Face.InternalBoundaries(flatFace)
|
1675
|
+
temp_verts = Topology.Vertices(eb)
|
1676
|
+
new_verts = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in temp_verts]
|
1677
|
+
eb = Wire.ByVertices(new_verts, close=True)
|
1678
|
+
new_ibList = []
|
1679
|
+
for ib in ibList:
|
1680
|
+
temp_verts = Topology.Vertices(ib)
|
1681
|
+
new_verts = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in temp_verts]
|
1682
|
+
new_ibList.append(Wire.ByVertices(new_verts, close=True))
|
1683
|
+
flatFace = Face.ByWires(eb, new_ibList)
|
1684
|
+
|
1685
|
+
# Create a cluster of the input vertices
|
1686
|
+
verticesCluster = Cluster.ByTopologies(vertices)
|
1687
|
+
|
1688
|
+
# Flatten the cluster using the same transformations
|
1689
|
+
verticesCluster = Topology.Flatten(verticesCluster, origin=origin, direction=normal)
|
1690
|
+
flatVertices = Topology.Vertices(verticesCluster)
|
1691
|
+
flatVertices = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in flatVertices]
|
1692
|
+
points = []
|
1693
|
+
for flatVertex in flatVertices:
|
1694
|
+
points.append([flatVertex.X(), flatVertex.Y()])
|
1695
|
+
|
1696
|
+
br = Wire.BoundingRectangle(flatFace)
|
1697
|
+
br_vertices = Wire.Vertices(br)
|
1698
|
+
br_x = []
|
1699
|
+
br_y = []
|
1700
|
+
for br_v in br_vertices:
|
1701
|
+
x, y = Vertex.Coordinates(br_v, outputType="xy")
|
1702
|
+
br_x.append(x)
|
1703
|
+
br_y.append(y)
|
1704
|
+
min_x = min(br_x)
|
1705
|
+
max_x = max(br_x)
|
1706
|
+
min_y = min(br_y)
|
1707
|
+
max_y = max(br_y)
|
1708
|
+
br_width = abs(max_x - min_x)
|
1709
|
+
br_length = abs(max_y - min_y)
|
1710
|
+
|
1711
|
+
points.append((-br_width*4, -br_length*4))
|
1712
|
+
points.append((-br_width*4, br_length*4))
|
1713
|
+
points.append((br_width*4, -br_length*4))
|
1714
|
+
points.append((br_width*4, br_length*4))
|
1715
|
+
|
1716
|
+
voronoi = Voronoi(points, furthest_site=False)
|
1717
|
+
voronoiVertices = []
|
1718
|
+
for v in voronoi.vertices:
|
1719
|
+
voronoiVertices.append(Vertex.ByCoordinates(v[0], v[1], 0))
|
1720
|
+
|
1721
|
+
faces = []
|
1722
|
+
for region in voronoi.regions:
|
1723
|
+
tempWire = []
|
1724
|
+
if len(region) > 1 and not -1 in region:
|
1725
|
+
for v in region:
|
1726
|
+
tempWire.append(Vertex.ByCoordinates(voronoiVertices[v].X(), voronoiVertices[v].Y(), 0))
|
1727
|
+
temp_verts = []
|
1728
|
+
for v in tempWire:
|
1729
|
+
if len(temp_verts) == 0:
|
1730
|
+
temp_verts.append(v)
|
1731
|
+
elif Vertex.Index(v, temp_verts) == None:
|
1732
|
+
temp_verts.append(v)
|
1733
|
+
tempWire = temp_verts
|
1734
|
+
temp_w = Wire.ByVertices(tempWire, close=True)
|
1735
|
+
faces.append(Face.ByWire(Wire.ByVertices(tempWire, close=True), tolerance=tolerance))
|
1736
|
+
shell = Shell.ByFaces(faces, tolerance=tolerance)
|
1737
|
+
edges = Shell.Edges(shell)
|
1738
|
+
edgesCluster = Cluster.ByTopologies(edges)
|
1739
|
+
shell = Topology.Slice(flatFace,edgesCluster, tolerance=tolerance)
|
1740
|
+
shell = Topology.Unflatten(shell, origin=origin, direction=normal)
|
1741
|
+
return shell
|
1742
|
+
|
1743
|
+
@staticmethod
|
1744
|
+
def Wires(shell: topologic.Shell) -> list:
|
1745
|
+
"""
|
1746
|
+
Returns the wires of the input shell.
|
1747
|
+
|
1748
|
+
Parameters
|
1749
|
+
----------
|
1750
|
+
shell : topologic.Shell
|
1751
|
+
The input shell.
|
1752
|
+
|
1753
|
+
Returns
|
1754
|
+
-------
|
1755
|
+
list
|
1756
|
+
The list of wires.
|
1757
|
+
|
1758
|
+
"""
|
1759
|
+
if not isinstance(shell, topologic.Shell):
|
1760
|
+
return None
|
1761
|
+
wires = []
|
1762
|
+
_ = shell.Wires(None, wires)
|
1763
|
+
return wires
|
1764
|
+
|
1765
|
+
|
1766
|
+
|
1767
|
+
|
1768
|
+
|
1769
1769
|
|