topologicpy 0.4.8__py3-none-any.whl → 0.4.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologicpy/Aperture.py +46 -0
- topologicpy/Cell.py +1780 -0
- topologicpy/CellComplex.py +791 -0
- topologicpy/Cluster.py +591 -0
- topologicpy/Color.py +157 -0
- topologicpy/Context.py +56 -0
- topologicpy/DGL.py +2661 -0
- topologicpy/Dictionary.py +470 -0
- topologicpy/Edge.py +855 -0
- topologicpy/EnergyModel.py +1052 -0
- topologicpy/Face.py +1810 -0
- topologicpy/Graph.py +3526 -0
- topologicpy/Graph_Export.py +858 -0
- topologicpy/Grid.py +338 -0
- topologicpy/Helper.py +182 -0
- topologicpy/Honeybee.py +424 -0
- topologicpy/Matrix.py +255 -0
- topologicpy/Neo4jGraph.py +311 -0
- topologicpy/Plotly.py +1396 -0
- topologicpy/Polyskel.py +524 -0
- topologicpy/Process.py +1368 -0
- topologicpy/SQL.py +48 -0
- topologicpy/Shell.py +1418 -0
- topologicpy/Speckle.py +433 -0
- topologicpy/Topology.py +5854 -0
- topologicpy/UnitTest.py +29 -0
- topologicpy/Vector.py +555 -0
- topologicpy/Vertex.py +714 -0
- topologicpy/Wire.py +2346 -0
- topologicpy/__init__.py +20 -0
- topologicpy/bin/linux/topologic/__init__.py +2 -0
- topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
- topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
- topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
- topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
- topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
- topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
- topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
- topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
- topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
- topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
- topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
- topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
- topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
- topologicpy/bin/windows/topologic/__init__.py +2 -0
- topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/METADATA +1 -1
- topologicpy-0.4.9.dist-info/RECORD +77 -0
- topologicpy-0.4.9.dist-info/top_level.txt +1 -0
- topologicpy-0.4.8.dist-info/RECORD +0 -5
- topologicpy-0.4.8.dist-info/top_level.txt +0 -1
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/LICENSE +0 -0
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/WHEEL +0 -0
topologicpy/Face.py
ADDED
|
@@ -0,0 +1,1810 @@
|
|
|
1
|
+
import topologicpy
|
|
2
|
+
import topologic
|
|
3
|
+
from topologicpy.Vector import Vector
|
|
4
|
+
from topologicpy.Wire import Wire
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
class Face(topologic.Face):
|
|
8
|
+
@staticmethod
|
|
9
|
+
def AddInternalBoundaries(face: topologic.Face, wires: list) -> topologic.Face:
|
|
10
|
+
"""
|
|
11
|
+
Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
face : topologic.Face
|
|
16
|
+
The input face.
|
|
17
|
+
wires : list
|
|
18
|
+
The input list of internal boundaries (closed wires).
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
topologic.Face
|
|
23
|
+
The created face with internal boundaries added to it.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
if not face:
|
|
27
|
+
return None
|
|
28
|
+
if not isinstance(face, topologic.Face):
|
|
29
|
+
return None
|
|
30
|
+
if not wires:
|
|
31
|
+
return face
|
|
32
|
+
if not isinstance(wires, list):
|
|
33
|
+
return face
|
|
34
|
+
wireList = [w for w in wires if isinstance(w, topologic.Wire)]
|
|
35
|
+
if len(wireList) < 1:
|
|
36
|
+
return face
|
|
37
|
+
faceeb = face.ExternalBoundary()
|
|
38
|
+
faceibList = []
|
|
39
|
+
_ = face.InternalBoundaries(faceibList)
|
|
40
|
+
for wire in wires:
|
|
41
|
+
faceibList.append(wire)
|
|
42
|
+
return topologic.Face.ByExternalInternalBoundaries(faceeb, faceibList)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) -> topologic.Face:
|
|
46
|
+
"""
|
|
47
|
+
Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
face : topologic.Face
|
|
52
|
+
The input face.
|
|
53
|
+
cluster : topologic.Cluster
|
|
54
|
+
The input cluster of internal boundaries (topologic wires).
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
topologic.Face
|
|
59
|
+
The created face with internal boundaries added to it.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
if not face:
|
|
63
|
+
return None
|
|
64
|
+
if not isinstance(face, topologic.Face):
|
|
65
|
+
return None
|
|
66
|
+
if not cluster:
|
|
67
|
+
return face
|
|
68
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
69
|
+
return face
|
|
70
|
+
wires = []
|
|
71
|
+
_ = cluster.Wires(None, wires)
|
|
72
|
+
return Face.AddInternalBoundaries(face, wires)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 4) -> float:
|
|
76
|
+
"""
|
|
77
|
+
Returns the angle in degrees between the two input faces.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
faceA : topologic.Face
|
|
82
|
+
The first input face.
|
|
83
|
+
faceB : topologic.Face
|
|
84
|
+
The second input face.
|
|
85
|
+
mantissa : int , optional
|
|
86
|
+
The desired length of the mantissa. The default is 4.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
float
|
|
91
|
+
The angle in degrees between the two input faces.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
from topologicpy.Vector import Vector
|
|
95
|
+
if not faceA or not isinstance(faceA, topologic.Face):
|
|
96
|
+
return None
|
|
97
|
+
if not faceB or not isinstance(faceB, topologic.Face):
|
|
98
|
+
return None
|
|
99
|
+
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
|
|
100
|
+
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
|
|
101
|
+
return round((Vector.Angle(dirA, dirB)), mantissa)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def Area(face: topologic.Face, mantissa: int = 4) -> float:
|
|
105
|
+
"""
|
|
106
|
+
Returns the area of the input face.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
face : topologic.Face
|
|
111
|
+
The input face.
|
|
112
|
+
mantissa : int , optional
|
|
113
|
+
The desired length of the mantissa. The default is 4.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
float
|
|
118
|
+
The area of the input face.
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
if not isinstance(face, topologic.Face):
|
|
122
|
+
return None
|
|
123
|
+
area = None
|
|
124
|
+
try:
|
|
125
|
+
area = round(topologic.FaceUtility.Area(face), mantissa)
|
|
126
|
+
except:
|
|
127
|
+
area = None
|
|
128
|
+
return area
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) -> topologic.Face:
|
|
132
|
+
"""
|
|
133
|
+
Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
topology : topologic.Topology
|
|
138
|
+
The input topology.
|
|
139
|
+
optimize : int , optional
|
|
140
|
+
If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
topologic.Face
|
|
145
|
+
The bounding rectangle of the input topology.
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
from topologicpy.Wire import Wire
|
|
149
|
+
from topologicpy.Face import Face
|
|
150
|
+
from topologicpy.Cluster import Cluster
|
|
151
|
+
from topologicpy.Topology import Topology
|
|
152
|
+
from topologicpy.Dictionary import Dictionary
|
|
153
|
+
def bb(topology):
|
|
154
|
+
vertices = []
|
|
155
|
+
_ = topology.Vertices(None, vertices)
|
|
156
|
+
x = []
|
|
157
|
+
y = []
|
|
158
|
+
for aVertex in vertices:
|
|
159
|
+
x.append(aVertex.X())
|
|
160
|
+
y.append(aVertex.Y())
|
|
161
|
+
minX = min(x)
|
|
162
|
+
minY = min(y)
|
|
163
|
+
maxX = max(x)
|
|
164
|
+
maxY = max(y)
|
|
165
|
+
return [minX, minY, maxX, maxY]
|
|
166
|
+
|
|
167
|
+
if not isinstance(topology, topologic.Topology):
|
|
168
|
+
return None
|
|
169
|
+
vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
|
|
170
|
+
topology = Cluster.ByTopologies(vertices)
|
|
171
|
+
boundingBox = bb(topology)
|
|
172
|
+
minX = boundingBox[0]
|
|
173
|
+
minY = boundingBox[1]
|
|
174
|
+
maxX = boundingBox[2]
|
|
175
|
+
maxY = boundingBox[3]
|
|
176
|
+
w = abs(maxX - minX)
|
|
177
|
+
l = abs(maxY - minY)
|
|
178
|
+
best_area = l*w
|
|
179
|
+
orig_area = best_area
|
|
180
|
+
best_z = 0
|
|
181
|
+
best_bb = boundingBox
|
|
182
|
+
origin = Topology.Centroid(topology)
|
|
183
|
+
optimize = min(max(optimize, 0), 10)
|
|
184
|
+
if optimize > 0:
|
|
185
|
+
factor = (round(((11 - optimize)/30 + 0.57), 2))
|
|
186
|
+
flag = False
|
|
187
|
+
for n in range(10,0,-1):
|
|
188
|
+
if flag:
|
|
189
|
+
break
|
|
190
|
+
za = n
|
|
191
|
+
zb = 90+n
|
|
192
|
+
zc = n
|
|
193
|
+
for z in range(za,zb,zc):
|
|
194
|
+
if flag:
|
|
195
|
+
break
|
|
196
|
+
t = Topology.Rotate(topology, origin=origin, x=0,y=0,z=1, degree=z)
|
|
197
|
+
minX, minY, maxX, maxY = bb(t)
|
|
198
|
+
w = abs(maxX - minX)
|
|
199
|
+
l = abs(maxY - minY)
|
|
200
|
+
area = l*w
|
|
201
|
+
if area < orig_area*factor:
|
|
202
|
+
best_area = area
|
|
203
|
+
best_z = z
|
|
204
|
+
best_bb = [minX, minY, maxX, maxY]
|
|
205
|
+
flag = True
|
|
206
|
+
break
|
|
207
|
+
if area < best_area:
|
|
208
|
+
best_area = area
|
|
209
|
+
best_z = z
|
|
210
|
+
best_bb = [minX, minY, maxX, maxY]
|
|
211
|
+
|
|
212
|
+
else:
|
|
213
|
+
best_bb = boundingBox
|
|
214
|
+
|
|
215
|
+
minX, minY, maxX, maxY = best_bb
|
|
216
|
+
vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
|
|
217
|
+
vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
|
|
218
|
+
vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
|
|
219
|
+
vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)
|
|
220
|
+
|
|
221
|
+
baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
|
|
222
|
+
baseFace = Face.ByWire(baseWire)
|
|
223
|
+
baseFace = Topology.Rotate(baseFace, origin=origin, x=0,y=0,z=1, degree=-best_z)
|
|
224
|
+
dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
|
|
225
|
+
baseFace = Topology.SetDictionary(baseFace, dictionary)
|
|
226
|
+
return baseFace
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def ByEdges(edges: list) -> topologic.Face:
|
|
230
|
+
"""
|
|
231
|
+
Creates a face from the input list of edges.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
edges : list
|
|
236
|
+
The input list of edges.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
face : topologic.Face
|
|
241
|
+
The created face.
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
from topologicpy.Wire import Wire
|
|
245
|
+
wire = Wire.ByEdges(edges)
|
|
246
|
+
if not wire:
|
|
247
|
+
return None
|
|
248
|
+
if not isinstance(wire, topologic.Wire):
|
|
249
|
+
return None
|
|
250
|
+
return Face.ByWire(wire)
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def ByEdgesCluster(cluster: topologic.Cluster) -> topologic.Face:
|
|
254
|
+
"""
|
|
255
|
+
Creates a face from the input cluster of edges.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
cluster : topologic.Cluster
|
|
260
|
+
The input cluster of edges.
|
|
261
|
+
|
|
262
|
+
Returns
|
|
263
|
+
-------
|
|
264
|
+
face : topologic.Face
|
|
265
|
+
The created face.
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
from topologicpy.Cluster import Cluster
|
|
269
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
270
|
+
return None
|
|
271
|
+
edges = Cluster.Edges(cluster)
|
|
272
|
+
return Face.ByEdges(edges)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) -> topologic.Face:
|
|
276
|
+
"""
|
|
277
|
+
Creates an offset wire from the input wire.
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
wire : topologic.Wire
|
|
282
|
+
The input wire.
|
|
283
|
+
offset : float , optional
|
|
284
|
+
The desired offset distance. The default is 1.0.
|
|
285
|
+
miter : bool , optional
|
|
286
|
+
if set to True, the corners will be mitered. The default is False.
|
|
287
|
+
miterThreshold : float , optional
|
|
288
|
+
The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
|
|
289
|
+
offsetKey : str , optional
|
|
290
|
+
If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
|
|
291
|
+
miterThresholdKey : str , optional
|
|
292
|
+
If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
|
|
293
|
+
step : bool , optional
|
|
294
|
+
If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
topologic.Wire
|
|
299
|
+
The created wire.
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
from topologicpy.Wire import Wire
|
|
303
|
+
|
|
304
|
+
eb = Face.Wire(face)
|
|
305
|
+
internal_boundaries = Face.InternalBoundaries(face)
|
|
306
|
+
offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
|
|
307
|
+
offset_internal_boundaries = []
|
|
308
|
+
for internal_boundary in internal_boundaries:
|
|
309
|
+
offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
|
|
310
|
+
return Face.ByWires(offset_external_boundary, offset_internal_boundaries)
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
def ByShell(shell: topologic.Shell, angTolerance: float = 0.1)-> topologic.Face:
|
|
314
|
+
"""
|
|
315
|
+
Creates a face by merging the faces of the input shell.
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
shell : topologic.Shell
|
|
320
|
+
The input shell.
|
|
321
|
+
angTolerance : float , optional
|
|
322
|
+
The desired angular tolerance. The default is 0.1.
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
topologic.Face
|
|
327
|
+
The created face.
|
|
328
|
+
|
|
329
|
+
"""
|
|
330
|
+
from topologicpy.Vertex import Vertex
|
|
331
|
+
from topologicpy.Wire import Wire
|
|
332
|
+
from topologicpy.Shell import Shell
|
|
333
|
+
from topologicpy.Topology import Topology
|
|
334
|
+
|
|
335
|
+
def planarizeList(wireList):
|
|
336
|
+
returnList = []
|
|
337
|
+
for aWire in wireList:
|
|
338
|
+
returnList.append(Wire.Planarize(aWire))
|
|
339
|
+
return returnList
|
|
340
|
+
|
|
341
|
+
ext_boundary = Shell.ExternalBoundary(shell)
|
|
342
|
+
ext_boundary = Wire.RemoveCollinearEdges(ext_boundary, angTolerance)
|
|
343
|
+
if not Topology.IsPlanar(ext_boundary):
|
|
344
|
+
ext_boundary = Wire.Planarize(ext_boundary)
|
|
345
|
+
|
|
346
|
+
if isinstance(ext_boundary, topologic.Wire):
|
|
347
|
+
try:
|
|
348
|
+
return topologic.Face.ByExternalBoundary(Wire.RemoveCollinearEdges(ext_boundary, angTolerance))
|
|
349
|
+
except:
|
|
350
|
+
try:
|
|
351
|
+
w = Wire.Planarize(ext_boundary)
|
|
352
|
+
f = Face.ByWire(w)
|
|
353
|
+
return f
|
|
354
|
+
except:
|
|
355
|
+
print("FaceByPlanarShell - Error: The input Wire is not planar and could not be fixed. Returning None.")
|
|
356
|
+
return None
|
|
357
|
+
elif isinstance(ext_boundary, topologic.Cluster):
|
|
358
|
+
wires = []
|
|
359
|
+
_ = ext_boundary.Wires(None, wires)
|
|
360
|
+
faces = []
|
|
361
|
+
areas = []
|
|
362
|
+
for aWire in wires:
|
|
363
|
+
try:
|
|
364
|
+
aFace = topologic.Face.ByExternalBoundary(Wire.RemoveCollinearEdges(aWire, angTolerance))
|
|
365
|
+
except:
|
|
366
|
+
aFace = topologic.Face.ByExternalBoundary(Wire.Planarize(Wire.RemoveCollinearEdges(aWire, angTolerance)))
|
|
367
|
+
anArea = topologic.FaceUtility.Area(aFace)
|
|
368
|
+
faces.append(aFace)
|
|
369
|
+
areas.append(anArea)
|
|
370
|
+
max_index = areas.index(max(areas))
|
|
371
|
+
ext_boundary = faces[max_index]
|
|
372
|
+
int_boundaries = list(set(faces) - set([ext_boundary]))
|
|
373
|
+
int_wires = []
|
|
374
|
+
for int_boundary in int_boundaries:
|
|
375
|
+
temp_wires = []
|
|
376
|
+
_ = int_boundary.Wires(None, temp_wires)
|
|
377
|
+
int_wires.append(Wire.RemoveCollinearEdges(temp_wires[0], angTolerance))
|
|
378
|
+
temp_wires = []
|
|
379
|
+
_ = ext_boundary.Wires(None, temp_wires)
|
|
380
|
+
ext_wire = Wire.RemoveCollinearEdges(temp_wires[0], angTolerance)
|
|
381
|
+
try:
|
|
382
|
+
return topologic.Face.ByExternalInternalBoundaries(ext_wire, int_wires)
|
|
383
|
+
except:
|
|
384
|
+
return topologic.Face.ByExternalInternalBoundaries(Wire.Planarize(ext_wire), planarizeList(int_wires))
|
|
385
|
+
else:
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
def ByVertices(vertices: list) -> topologic.Face:
|
|
390
|
+
|
|
391
|
+
"""
|
|
392
|
+
Creates a face from the input list of vertices.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
vertices : list
|
|
397
|
+
The input list of vertices.
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
topologic.Face
|
|
402
|
+
The created face.
|
|
403
|
+
|
|
404
|
+
"""
|
|
405
|
+
from topologicpy.Topology import Topology
|
|
406
|
+
from topologicpy.Wire import Wire
|
|
407
|
+
|
|
408
|
+
if not isinstance(vertices, list):
|
|
409
|
+
return None
|
|
410
|
+
vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
|
411
|
+
if len(vertexList) < 3:
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
w = Wire.ByVertices(vertexList)
|
|
415
|
+
f = Face.ByExternalBoundary(w)
|
|
416
|
+
return f
|
|
417
|
+
|
|
418
|
+
@staticmethod
|
|
419
|
+
def ByVerticesCluster(cluster: topologic.Cluster) -> topologic.Face:
|
|
420
|
+
"""
|
|
421
|
+
Creates a face from the input cluster of vertices.
|
|
422
|
+
|
|
423
|
+
Parameters
|
|
424
|
+
----------
|
|
425
|
+
cluster : topologic.Cluster
|
|
426
|
+
The input cluster of vertices.
|
|
427
|
+
|
|
428
|
+
Returns
|
|
429
|
+
-------
|
|
430
|
+
topologic.Face
|
|
431
|
+
The crearted face.
|
|
432
|
+
|
|
433
|
+
"""
|
|
434
|
+
from topologicpy.Cluster import Cluster
|
|
435
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
436
|
+
return None
|
|
437
|
+
vertices = Cluster.Vertices(cluster)
|
|
438
|
+
return Face.ByVertices(vertices)
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
def ByWire(wire: topologic.Wire) -> topologic.Face:
|
|
442
|
+
"""
|
|
443
|
+
Creates a face from the input closed wire.
|
|
444
|
+
|
|
445
|
+
Parameters
|
|
446
|
+
----------
|
|
447
|
+
wire : topologic.Wire
|
|
448
|
+
The input wire.
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
topologic.Face or list
|
|
453
|
+
The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.
|
|
454
|
+
|
|
455
|
+
"""
|
|
456
|
+
from topologicpy.Vertex import Vertex
|
|
457
|
+
from topologicpy.Wire import Wire
|
|
458
|
+
from topologicpy.Shell import Shell
|
|
459
|
+
from topologicpy.Cluster import Cluster
|
|
460
|
+
from topologicpy.Topology import Topology
|
|
461
|
+
from topologicpy.Dictionary import Dictionary
|
|
462
|
+
import random
|
|
463
|
+
|
|
464
|
+
def triangulateWire(wire):
|
|
465
|
+
wire = Wire.RemoveCollinearEdges(wire)
|
|
466
|
+
vertices = Wire.Vertices(wire)
|
|
467
|
+
shell = Shell.Delaunay(vertices)
|
|
468
|
+
if isinstance(shell, topologic.Shell):
|
|
469
|
+
return Shell.Faces(shell)
|
|
470
|
+
else:
|
|
471
|
+
return []
|
|
472
|
+
if not isinstance(wire, topologic.Wire):
|
|
473
|
+
return None
|
|
474
|
+
if not Wire.IsClosed(wire):
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
edges = Wire.Edges(wire)
|
|
478
|
+
wire = Topology.SelfMerge(Cluster.ByTopologies(edges))
|
|
479
|
+
vertices = Wire.Vertices(wire)
|
|
480
|
+
#print("This wire has:", len(vertices), "vertices.")
|
|
481
|
+
try:
|
|
482
|
+
#print(Topology.IsPlanar(wire))
|
|
483
|
+
fList = topologic.Face.ByExternalBoundary(wire)
|
|
484
|
+
except:
|
|
485
|
+
if len(vertices) > 3:
|
|
486
|
+
print("This wire has:", len(vertices), "vertices.")
|
|
487
|
+
print("Non planar wire, triangulating")
|
|
488
|
+
fList = triangulateWire(wire)
|
|
489
|
+
print("After triangulation", fList)
|
|
490
|
+
else:
|
|
491
|
+
fList = []
|
|
492
|
+
|
|
493
|
+
if not isinstance(fList, list):
|
|
494
|
+
fList = [fList]
|
|
495
|
+
|
|
496
|
+
returnList = []
|
|
497
|
+
for f in fList:
|
|
498
|
+
if Face.Area(f) < 0:
|
|
499
|
+
wire = Face.ExternalBoundary(f)
|
|
500
|
+
wire = Wire.Invert(wire)
|
|
501
|
+
try:
|
|
502
|
+
f = topologic.Face.ByExternalBoundary(wire)
|
|
503
|
+
returnList.append(f)
|
|
504
|
+
except:
|
|
505
|
+
pass
|
|
506
|
+
else:
|
|
507
|
+
returnList.append(f)
|
|
508
|
+
if len(returnList) == 0:
|
|
509
|
+
return None
|
|
510
|
+
elif len(returnList) == 1:
|
|
511
|
+
return returnList[0]
|
|
512
|
+
else:
|
|
513
|
+
return returnList
|
|
514
|
+
|
|
515
|
+
@staticmethod
|
|
516
|
+
def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = []) -> topologic.Face:
|
|
517
|
+
"""
|
|
518
|
+
Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).
|
|
519
|
+
|
|
520
|
+
Parameters
|
|
521
|
+
----------
|
|
522
|
+
externalBoundary : topologic.Wire
|
|
523
|
+
The input external boundary.
|
|
524
|
+
internalBoundaries : list , optional
|
|
525
|
+
The input list of internal boundaries (closed wires). The default is an empty list.
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
topologic.Face
|
|
530
|
+
The created face.
|
|
531
|
+
|
|
532
|
+
"""
|
|
533
|
+
if not isinstance(externalBoundary, topologic.Wire):
|
|
534
|
+
return None
|
|
535
|
+
if not Wire.IsClosed(externalBoundary):
|
|
536
|
+
return None
|
|
537
|
+
ibList = [x for x in internalBoundaries if isinstance(x, topologic.Wire) and Wire.IsClosed(x)]
|
|
538
|
+
return topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList)
|
|
539
|
+
|
|
540
|
+
@staticmethod
|
|
541
|
+
def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None) -> topologic.Face:
|
|
542
|
+
"""
|
|
543
|
+
Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).
|
|
544
|
+
|
|
545
|
+
Parameters
|
|
546
|
+
----------
|
|
547
|
+
externalBoundary : topologic.Wire
|
|
548
|
+
The input external boundary (closed wire).
|
|
549
|
+
internalBoundariesCluster : topologic.Cluster
|
|
550
|
+
The input cluster of internal boundaries (closed wires). The default is None.
|
|
551
|
+
|
|
552
|
+
Returns
|
|
553
|
+
-------
|
|
554
|
+
topologic.Face
|
|
555
|
+
The created face.
|
|
556
|
+
|
|
557
|
+
"""
|
|
558
|
+
from topologicpy.Wire import Wire
|
|
559
|
+
from topologicpy.Cluster import Cluster
|
|
560
|
+
if not isinstance(externalBoundary, topologic.Wire):
|
|
561
|
+
return None
|
|
562
|
+
if not Wire.IsClosed(externalBoundary):
|
|
563
|
+
return None
|
|
564
|
+
if not internalBoundariesCluster:
|
|
565
|
+
internalBoundaries = []
|
|
566
|
+
elif not isinstance(internalBoundariesCluster, topologic.Cluster):
|
|
567
|
+
return None
|
|
568
|
+
else:
|
|
569
|
+
internalBoundaries = Cluster.Wires(internalBoundariesCluster)
|
|
570
|
+
return Face.ByWires(externalBoundary, internalBoundaries)
|
|
571
|
+
|
|
572
|
+
@staticmethod
|
|
573
|
+
def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0,0,1],
|
|
574
|
+
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
|
575
|
+
"""
|
|
576
|
+
Creates a circle.
|
|
577
|
+
|
|
578
|
+
Parameters
|
|
579
|
+
----------
|
|
580
|
+
origin : topologic.Vertex, optional
|
|
581
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
|
|
582
|
+
radius : float , optional
|
|
583
|
+
The radius of the circle. The default is 1.
|
|
584
|
+
sides : int , optional
|
|
585
|
+
The number of sides of the circle. The default is 16.
|
|
586
|
+
fromAngle : float , optional
|
|
587
|
+
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
|
588
|
+
toAngle : float , optional
|
|
589
|
+
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
|
590
|
+
direction : list , optional
|
|
591
|
+
The vector representing the up direction of the circle. The default is [0,0,1].
|
|
592
|
+
placement : str , optional
|
|
593
|
+
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
|
|
594
|
+
tolerance : float , optional
|
|
595
|
+
The desired tolerance. The default is 0.0001.
|
|
596
|
+
|
|
597
|
+
Returns
|
|
598
|
+
-------
|
|
599
|
+
topologic.Face
|
|
600
|
+
The created circle.
|
|
601
|
+
|
|
602
|
+
"""
|
|
603
|
+
from topologicpy.Wire import Wire
|
|
604
|
+
wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
|
|
605
|
+
if not isinstance(wire, topologic.Wire):
|
|
606
|
+
return None
|
|
607
|
+
return Face.ByWire(wire)
|
|
608
|
+
|
|
609
|
+
@staticmethod
|
|
610
|
+
def Compactness(face: topologic.Face, mantissa: int = 4) -> float:
|
|
611
|
+
"""
|
|
612
|
+
Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
face : topologic.Face
|
|
617
|
+
The input face.
|
|
618
|
+
mantissa : int , optional
|
|
619
|
+
The desired length of the mantissa. The default is 4.
|
|
620
|
+
|
|
621
|
+
Returns
|
|
622
|
+
-------
|
|
623
|
+
float
|
|
624
|
+
The compactness measure of the input face.
|
|
625
|
+
|
|
626
|
+
"""
|
|
627
|
+
exb = face.ExternalBoundary()
|
|
628
|
+
edges = []
|
|
629
|
+
_ = exb.Edges(None, edges)
|
|
630
|
+
perimeter = 0.0
|
|
631
|
+
for anEdge in edges:
|
|
632
|
+
perimeter = perimeter + abs(topologic.EdgeUtility.Length(anEdge))
|
|
633
|
+
area = abs(topologic.FaceUtility.Area(face))
|
|
634
|
+
compactness = 0
|
|
635
|
+
#From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
|
636
|
+
|
|
637
|
+
if area <= 0:
|
|
638
|
+
return None
|
|
639
|
+
if perimeter <= 0:
|
|
640
|
+
return None
|
|
641
|
+
compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
|
|
642
|
+
return round(compactness, mantissa)
|
|
643
|
+
|
|
644
|
+
@staticmethod
|
|
645
|
+
def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 4) -> float:
|
|
646
|
+
"""
|
|
647
|
+
Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.
|
|
648
|
+
|
|
649
|
+
Parameters
|
|
650
|
+
----------
|
|
651
|
+
face : topologic.Face
|
|
652
|
+
The input face.
|
|
653
|
+
north : list , optional
|
|
654
|
+
The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
|
|
655
|
+
mantissa : int, optional
|
|
656
|
+
The length of the desired mantissa. The default is 4.
|
|
657
|
+
tolerance : float , optional
|
|
658
|
+
The desired tolerance. The default is 0.0001.
|
|
659
|
+
|
|
660
|
+
Returns
|
|
661
|
+
-------
|
|
662
|
+
float
|
|
663
|
+
The horizontal compass angle in degrees between the direction of the face and the second input vector.
|
|
664
|
+
|
|
665
|
+
"""
|
|
666
|
+
from topologicpy.Vector import Vector
|
|
667
|
+
if not isinstance(face, topologic.Face):
|
|
668
|
+
return None
|
|
669
|
+
if not north:
|
|
670
|
+
north = Vector.North()
|
|
671
|
+
dirA = Face.NormalAtParameters(face,mantissa=mantissa)
|
|
672
|
+
return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)
|
|
673
|
+
|
|
674
|
+
@staticmethod
|
|
675
|
+
def Edges(face: topologic.Face) -> list:
|
|
676
|
+
"""
|
|
677
|
+
Returns the edges of the input face.
|
|
678
|
+
|
|
679
|
+
Parameters
|
|
680
|
+
----------
|
|
681
|
+
face : topologic.Face
|
|
682
|
+
The input face.
|
|
683
|
+
|
|
684
|
+
Returns
|
|
685
|
+
-------
|
|
686
|
+
list
|
|
687
|
+
The list of edges.
|
|
688
|
+
|
|
689
|
+
"""
|
|
690
|
+
if not isinstance(face, topologic.Face):
|
|
691
|
+
return None
|
|
692
|
+
edges = []
|
|
693
|
+
_ = face.Edges(None, edges)
|
|
694
|
+
return edges
|
|
695
|
+
|
|
696
|
+
@staticmethod
|
|
697
|
+
def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0,0,1], placement: str = "center") -> topologic.Face:
|
|
698
|
+
"""
|
|
699
|
+
Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798
|
|
700
|
+
|
|
701
|
+
Parameters
|
|
702
|
+
----------
|
|
703
|
+
origin : topologic.Vertex , optional
|
|
704
|
+
The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0,0,0).
|
|
705
|
+
radius : float , optional
|
|
706
|
+
The radius of the hexagon determining the size of the tile. The default is 0.5.
|
|
707
|
+
direction : list , optional
|
|
708
|
+
The vector representing the up direction of the ellipse. The default is [0,0,1].
|
|
709
|
+
placement : str , optional
|
|
710
|
+
The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
711
|
+
|
|
712
|
+
"""
|
|
713
|
+
from topologicpy.Wire import Wire
|
|
714
|
+
wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
|
|
715
|
+
if not isinstance(wire, topologic.Wire):
|
|
716
|
+
return None
|
|
717
|
+
return Face.ByWire(wire)
|
|
718
|
+
|
|
719
|
+
@staticmethod
|
|
720
|
+
def ExternalBoundary(face: topologic.Face) -> topologic.Wire:
|
|
721
|
+
"""
|
|
722
|
+
Returns the external boundary (closed wire) of the input face.
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
face : topologic.Face
|
|
727
|
+
The input face.
|
|
728
|
+
|
|
729
|
+
Returns
|
|
730
|
+
-------
|
|
731
|
+
topologic.Wire
|
|
732
|
+
The external boundary of the input face.
|
|
733
|
+
|
|
734
|
+
"""
|
|
735
|
+
return face.ExternalBoundary()
|
|
736
|
+
|
|
737
|
+
@staticmethod
|
|
738
|
+
def FacingToward(face: topologic.Face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
|
|
739
|
+
"""
|
|
740
|
+
Returns True if the input face is facing toward the input direction.
|
|
741
|
+
|
|
742
|
+
Parameters
|
|
743
|
+
----------
|
|
744
|
+
face : topologic.Face
|
|
745
|
+
The input face.
|
|
746
|
+
direction : list , optional
|
|
747
|
+
The input direction. The default is [0,0,-1].
|
|
748
|
+
asVertex : bool , optional
|
|
749
|
+
If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
|
|
750
|
+
tolerance : float , optional
|
|
751
|
+
The desired tolerance. The default is 0.0001.
|
|
752
|
+
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
bool
|
|
756
|
+
True if the face is facing toward the direction. False otherwise.
|
|
757
|
+
|
|
758
|
+
"""
|
|
759
|
+
faceNormal = topologic.FaceUtility.NormalAtParameters(face,0.5, 0.5)
|
|
760
|
+
faceCenter = topologic.FaceUtility.VertexAtParameters(face,0.5,0.5)
|
|
761
|
+
cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
|
|
762
|
+
try:
|
|
763
|
+
vList = [direction.X(), direction.Y(), direction.Z()]
|
|
764
|
+
except:
|
|
765
|
+
try:
|
|
766
|
+
vList = [direction[0], direction[1], direction[2]]
|
|
767
|
+
except:
|
|
768
|
+
raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
|
|
769
|
+
if asVertex:
|
|
770
|
+
dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
|
|
771
|
+
else:
|
|
772
|
+
dV = vList
|
|
773
|
+
uV = Vector.Normalize(dV)
|
|
774
|
+
dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
|
|
775
|
+
if dot < tolerance:
|
|
776
|
+
return False
|
|
777
|
+
return True
|
|
778
|
+
|
|
779
|
+
@staticmethod
|
|
780
|
+
def Flatten(face: topologic.Face, originA: topologic.Vertex = None, originB: topologic.Vertex = None, direction: list = None) -> topologic.Face:
|
|
781
|
+
"""
|
|
782
|
+
Flattens the input face such that its center of mass is located at the origin and its normal is pointed in the positive Z axis.
|
|
783
|
+
|
|
784
|
+
Parameters
|
|
785
|
+
----------
|
|
786
|
+
face : topologic.Face
|
|
787
|
+
The input face.
|
|
788
|
+
originA : topologic.Vertex , optional
|
|
789
|
+
The old location to use as the origin of the movement. If set to None, the center of mass of the input topology is used. The default is None.
|
|
790
|
+
originB : topologic.Vertex , optional
|
|
791
|
+
The new location at which to place the topology. If set to None, the world origin (0,0,0) is used. The default is None.
|
|
792
|
+
direction : list , optional
|
|
793
|
+
The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.
|
|
794
|
+
|
|
795
|
+
Returns
|
|
796
|
+
-------
|
|
797
|
+
topologic.Face
|
|
798
|
+
The flattened face.
|
|
799
|
+
|
|
800
|
+
"""
|
|
801
|
+
|
|
802
|
+
def leftMost(vertices, tolerance = 0.0001):
|
|
803
|
+
xCoords = []
|
|
804
|
+
for v in vertices:
|
|
805
|
+
xCoords.append(Vertex.Coordinates(vertices[0])[0])
|
|
806
|
+
minX = min(xCoords)
|
|
807
|
+
lmVertices = []
|
|
808
|
+
for v in vertices:
|
|
809
|
+
if abs(Vertex.Coordinates(vertices[0])[0] - minX) <= tolerance:
|
|
810
|
+
lmVertices.append(v)
|
|
811
|
+
return lmVertices
|
|
812
|
+
|
|
813
|
+
def bottomMost(vertices, tolerance = 0.0001):
|
|
814
|
+
yCoords = []
|
|
815
|
+
for v in vertices:
|
|
816
|
+
yCoords.append(Vertex.Coordinates(vertices[0])[1])
|
|
817
|
+
minY = min(yCoords)
|
|
818
|
+
bmVertices = []
|
|
819
|
+
for v in vertices:
|
|
820
|
+
if abs(Vertex.Coordinates(vertices[0])[1] - minY) <= tolerance:
|
|
821
|
+
bmVertices.append(v)
|
|
822
|
+
return bmVertices
|
|
823
|
+
|
|
824
|
+
def vIndex(v, vList, tolerance):
|
|
825
|
+
for i in range(len(vList)):
|
|
826
|
+
if topologic.VertexUtility.Distance(v, vList[i]) < tolerance:
|
|
827
|
+
return i+1
|
|
828
|
+
return None
|
|
829
|
+
|
|
830
|
+
# rotate cycle path such that it begins with the smallest node
|
|
831
|
+
def rotate_to_smallest(path):
|
|
832
|
+
n = path.index(min(path))
|
|
833
|
+
return path[n:]+path[:n]
|
|
834
|
+
|
|
835
|
+
# rotate vertices list so that it begins with the input vertex
|
|
836
|
+
def rotate_vertices(vertices, vertex):
|
|
837
|
+
n = vertices.index(vertex)
|
|
838
|
+
return vertices[n:]+vertices[:n]
|
|
839
|
+
|
|
840
|
+
from topologicpy.Vertex import Vertex
|
|
841
|
+
from topologicpy.Topology import Topology
|
|
842
|
+
from topologicpy.Dictionary import Dictionary
|
|
843
|
+
if not isinstance(face, topologic.Face):
|
|
844
|
+
return None
|
|
845
|
+
if not isinstance(originA, topologic.Vertex):
|
|
846
|
+
originA = Topology.CenterOfMass(face)
|
|
847
|
+
if not isinstance(originB, topologic.Vertex):
|
|
848
|
+
originB = Vertex.ByCoordinates(0,0,0)
|
|
849
|
+
cm = originA
|
|
850
|
+
world_origin = originB
|
|
851
|
+
if not direction or len(direction) < 3:
|
|
852
|
+
direction = Face.NormalAtParameters(face, 0.5, 0.5)
|
|
853
|
+
x1 = Vertex.X(cm)
|
|
854
|
+
y1 = Vertex.Y(cm)
|
|
855
|
+
z1 = Vertex.Z(cm)
|
|
856
|
+
x2 = Vertex.X(cm) + direction[0]
|
|
857
|
+
y2 = Vertex.Y(cm) + direction[1]
|
|
858
|
+
z2 = Vertex.Z(cm) + direction[2]
|
|
859
|
+
dx = x2 - x1
|
|
860
|
+
dy = y2 - y1
|
|
861
|
+
dz = z2 - z1
|
|
862
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
863
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
864
|
+
if dist < 0.0001:
|
|
865
|
+
theta = 0
|
|
866
|
+
else:
|
|
867
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
868
|
+
flatFace = Topology.Translate(face, -cm.X(), -cm.Y(), -cm.Z())
|
|
869
|
+
flatFace = Topology.Rotate(flatFace, world_origin, 0, 0, 1, -phi)
|
|
870
|
+
flatFace = Topology.Rotate(flatFace, world_origin, 0, 1, 0, -theta)
|
|
871
|
+
# Ensure flatness. Force Z to be zero
|
|
872
|
+
flatExternalBoundary = Face.ExternalBoundary(flatFace)
|
|
873
|
+
flatFaceVertices = Topology.SubTopologies(flatExternalBoundary, subTopologyType="vertex")
|
|
874
|
+
|
|
875
|
+
tempVertices = []
|
|
876
|
+
for ffv in flatFaceVertices:
|
|
877
|
+
tempVertices.append(Vertex.ByCoordinates(ffv.X(), ffv.Y(), 0))
|
|
878
|
+
|
|
879
|
+
temp_v = bottomMost(leftMost(tempVertices))[0]
|
|
880
|
+
tempVertices = rotate_vertices(tempVertices, temp_v)
|
|
881
|
+
flatExternalBoundary = Wire.ByVertices(tempVertices)
|
|
882
|
+
|
|
883
|
+
internalBoundaries = Face.InternalBoundaries(flatFace)
|
|
884
|
+
flatInternalBoundaries = []
|
|
885
|
+
for internalBoundary in internalBoundaries:
|
|
886
|
+
ibVertices = Wire.Vertices(internalBoundary)
|
|
887
|
+
tempVertices = []
|
|
888
|
+
for ibVertex in ibVertices:
|
|
889
|
+
tempVertices.append(Vertex.ByCoordinates(ibVertex.X(), ibVertex.Y(), 0))
|
|
890
|
+
temp_v = bottomMost(leftMost(tempVertices))[0]
|
|
891
|
+
tempVertices = rotate_vertices(tempVertices, temp_v)
|
|
892
|
+
flatInternalBoundaries.append(Wire.ByVertices(tempVertices))
|
|
893
|
+
flatFace = Face.ByWires(flatExternalBoundary, flatInternalBoundaries)
|
|
894
|
+
dictionary = Dictionary.ByKeysValues(["xTran", "yTran", "zTran", "phi", "theta"], [cm.X(), cm.Y(), cm.Z(), phi, theta])
|
|
895
|
+
flatFace = Topology.SetDictionary(flatFace, dictionary)
|
|
896
|
+
return flatFace
|
|
897
|
+
|
|
898
|
+
@staticmethod
|
|
899
|
+
def Harmonize(face: topologic.Face) -> topologic.Face:
|
|
900
|
+
"""
|
|
901
|
+
Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.
|
|
902
|
+
|
|
903
|
+
Parameters
|
|
904
|
+
----------
|
|
905
|
+
face : topologic.Face
|
|
906
|
+
The input face.
|
|
907
|
+
|
|
908
|
+
Returns
|
|
909
|
+
-------
|
|
910
|
+
topologic.Face
|
|
911
|
+
The harmonized face.
|
|
912
|
+
|
|
913
|
+
"""
|
|
914
|
+
from topologicpy.Vertex import Vertex
|
|
915
|
+
from topologicpy.Wire import Wire
|
|
916
|
+
from topologicpy.Topology import Topology
|
|
917
|
+
from topologicpy.Dictionary import Dictionary
|
|
918
|
+
|
|
919
|
+
if not isinstance(face, topologic.Face):
|
|
920
|
+
return None
|
|
921
|
+
flatFace = Face.Flatten(face)
|
|
922
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
923
|
+
# Retrieve the needed transformations
|
|
924
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
925
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
926
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
927
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
928
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
929
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
930
|
+
vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
|
|
931
|
+
harmonizedEB = Wire.ByVertices(vertices)
|
|
932
|
+
internalBoundaries = Face.InternalBoundaries(flatFace)
|
|
933
|
+
harmonizedIB = []
|
|
934
|
+
for ib in internalBoundaries:
|
|
935
|
+
ibVertices = Wire.Vertices(ib)
|
|
936
|
+
harmonizedIB.append(Wire.ByVertices(ibVertices))
|
|
937
|
+
harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB)
|
|
938
|
+
harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
939
|
+
harmonizedFace = Topology.Rotate(harmonizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
940
|
+
harmonizedFace = Topology.Translate(harmonizedFace, xTran, yTran, zTran)
|
|
941
|
+
return harmonizedFace
|
|
942
|
+
|
|
943
|
+
@staticmethod
|
|
944
|
+
def InternalBoundaries(face: topologic.Face) -> list:
|
|
945
|
+
"""
|
|
946
|
+
Returns the internal boundaries (closed wires) of the input face.
|
|
947
|
+
|
|
948
|
+
Parameters
|
|
949
|
+
----------
|
|
950
|
+
face : topologic.Face
|
|
951
|
+
The input face.
|
|
952
|
+
|
|
953
|
+
Returns
|
|
954
|
+
-------
|
|
955
|
+
list
|
|
956
|
+
The list of internal boundaries (closed wires).
|
|
957
|
+
|
|
958
|
+
"""
|
|
959
|
+
if not isinstance(face, topologic.Face):
|
|
960
|
+
return None
|
|
961
|
+
wires = []
|
|
962
|
+
_ = face.InternalBoundaries(wires)
|
|
963
|
+
return list(wires)
|
|
964
|
+
|
|
965
|
+
@staticmethod
|
|
966
|
+
def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Vertex:
|
|
967
|
+
"""
|
|
968
|
+
Creates a vertex guaranteed to be inside the input face.
|
|
969
|
+
|
|
970
|
+
Parameters
|
|
971
|
+
----------
|
|
972
|
+
face : topologic.Face
|
|
973
|
+
The input face.
|
|
974
|
+
tolerance : float , optional
|
|
975
|
+
The desired tolerance. The default is 0.0001.
|
|
976
|
+
|
|
977
|
+
Returns
|
|
978
|
+
-------
|
|
979
|
+
topologic.Vertex
|
|
980
|
+
The created vertex.
|
|
981
|
+
|
|
982
|
+
"""
|
|
983
|
+
if not isinstance(face, topologic.Face):
|
|
984
|
+
return None
|
|
985
|
+
v = topologic.FaceUtility.InternalVertex(face, tolerance)
|
|
986
|
+
return v
|
|
987
|
+
|
|
988
|
+
@staticmethod
|
|
989
|
+
def Invert(face: topologic.Face) -> topologic.Face:
|
|
990
|
+
"""
|
|
991
|
+
Creates a face that is an inverse (mirror) of the input face.
|
|
992
|
+
|
|
993
|
+
Parameters
|
|
994
|
+
----------
|
|
995
|
+
face : topologic.Face
|
|
996
|
+
The input face.
|
|
997
|
+
|
|
998
|
+
Returns
|
|
999
|
+
-------
|
|
1000
|
+
topologic.Face
|
|
1001
|
+
The inverted face.
|
|
1002
|
+
|
|
1003
|
+
"""
|
|
1004
|
+
from topologicpy.Wire import Wire
|
|
1005
|
+
|
|
1006
|
+
if not isinstance(face, topologic.Face):
|
|
1007
|
+
return None
|
|
1008
|
+
eb = Face.ExternalBoundary(face)
|
|
1009
|
+
vertices = Wire.Vertices(eb)
|
|
1010
|
+
vertices.reverse()
|
|
1011
|
+
inverted_wire = Wire.ByVertices(vertices)
|
|
1012
|
+
internal_boundaries = Face.InternalBoundaries(face)
|
|
1013
|
+
if not internal_boundaries:
|
|
1014
|
+
inverted_face = Face.ByWire(inverted_wire)
|
|
1015
|
+
else:
|
|
1016
|
+
inverted_face = Face.ByWires(inverted_wire, internal_boundaries)
|
|
1017
|
+
return inverted_face
|
|
1018
|
+
|
|
1019
|
+
@staticmethod
|
|
1020
|
+
def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) -> bool:
|
|
1021
|
+
"""
|
|
1022
|
+
Returns True if the two input faces are coplanar. Returns False otherwise.
|
|
1023
|
+
|
|
1024
|
+
Parameters
|
|
1025
|
+
----------
|
|
1026
|
+
faceA : topologic.Face
|
|
1027
|
+
The first input face.
|
|
1028
|
+
faceB : topologic.Face
|
|
1029
|
+
The second input face
|
|
1030
|
+
tolerance : float , optional
|
|
1031
|
+
The desired tolerance. The deafault is 0.0001.
|
|
1032
|
+
|
|
1033
|
+
Raises
|
|
1034
|
+
------
|
|
1035
|
+
Exception
|
|
1036
|
+
Raises an exception if the angle between the two input faces cannot be determined.
|
|
1037
|
+
|
|
1038
|
+
Returns
|
|
1039
|
+
-------
|
|
1040
|
+
bool
|
|
1041
|
+
True if the two input faces are coplanar. False otherwise.
|
|
1042
|
+
|
|
1043
|
+
"""
|
|
1044
|
+
if not isinstance(faceA, topologic.Face) or not isinstance(faceB, topologic.Face):
|
|
1045
|
+
return None
|
|
1046
|
+
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
|
|
1047
|
+
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
|
|
1048
|
+
return Vector.IsCollinear(dirA, dirB, tolerance)
|
|
1049
|
+
|
|
1050
|
+
@staticmethod
|
|
1051
|
+
def IsInside(face: topologic.Face, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
|
1052
|
+
"""
|
|
1053
|
+
Returns True if the input vertex is inside the input face. Returns False otherwise.
|
|
1054
|
+
|
|
1055
|
+
Parameters
|
|
1056
|
+
----------
|
|
1057
|
+
face : topologic.Face
|
|
1058
|
+
The input face.
|
|
1059
|
+
vertex : topologic.Vertex
|
|
1060
|
+
The input vertex.
|
|
1061
|
+
tolerance : float , optional
|
|
1062
|
+
The desired tolerance. The default is 0.0001.
|
|
1063
|
+
|
|
1064
|
+
Returns
|
|
1065
|
+
-------
|
|
1066
|
+
bool
|
|
1067
|
+
True if the input vertex is inside the input face. False otherwise.
|
|
1068
|
+
|
|
1069
|
+
"""
|
|
1070
|
+
|
|
1071
|
+
# Ray tracing from https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
|
|
1072
|
+
def ray_tracing_method(x,y,poly):
|
|
1073
|
+
n = len(poly)
|
|
1074
|
+
inside = False
|
|
1075
|
+
|
|
1076
|
+
p1x,p1y = poly[0]
|
|
1077
|
+
for i in range(n+1):
|
|
1078
|
+
p2x,p2y = poly[i % n]
|
|
1079
|
+
if y > min(p1y,p2y):
|
|
1080
|
+
if y <= max(p1y,p2y):
|
|
1081
|
+
if x <= max(p1x,p2x):
|
|
1082
|
+
if p1y != p2y:
|
|
1083
|
+
xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
|
|
1084
|
+
if p1x == p2x or x <= xints:
|
|
1085
|
+
inside = not inside
|
|
1086
|
+
p1x,p1y = p2x,p2y
|
|
1087
|
+
|
|
1088
|
+
return inside
|
|
1089
|
+
|
|
1090
|
+
from topologicpy.Vertex import Vertex
|
|
1091
|
+
from topologicpy.Topology import Topology
|
|
1092
|
+
from topologicpy.Dictionary import Dictionary
|
|
1093
|
+
|
|
1094
|
+
if not isinstance(face, topologic.Face):
|
|
1095
|
+
return None
|
|
1096
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
1097
|
+
return None
|
|
1098
|
+
|
|
1099
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
1100
|
+
# Flatten face and vertex
|
|
1101
|
+
flatFace = Face.Flatten(face)
|
|
1102
|
+
# Retrieve the needed transformations
|
|
1103
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
1104
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
1105
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
1106
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
1107
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
1108
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
1109
|
+
|
|
1110
|
+
vertex = Topology.Translate(vertex, -xTran, -yTran, -zTran)
|
|
1111
|
+
vertex = Topology.Rotate(vertex, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
1112
|
+
vertex = Topology.Rotate(vertex, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
1113
|
+
|
|
1114
|
+
# Test if Vertex is hovering above or below face
|
|
1115
|
+
if abs(Vertex.Z(vertex)) > tolerance:
|
|
1116
|
+
return False
|
|
1117
|
+
|
|
1118
|
+
# Build 2D poly from flat face
|
|
1119
|
+
wire = Face.ExternalBoundary(flatFace)
|
|
1120
|
+
vertices = Wire.Vertices(wire)
|
|
1121
|
+
poly = []
|
|
1122
|
+
for v in vertices:
|
|
1123
|
+
poly.append([Vertex.X(v), Vertex.Y(v)])
|
|
1124
|
+
|
|
1125
|
+
# Use ray tracing method to test if vertex is inside the face
|
|
1126
|
+
status = ray_tracing_method(Vertex.X(vertex), Vertex.Y(vertex), poly)
|
|
1127
|
+
# Vertex is not inside
|
|
1128
|
+
if not status:
|
|
1129
|
+
return status
|
|
1130
|
+
|
|
1131
|
+
# If it is inside, we must check if it is inside a hole in the face
|
|
1132
|
+
internal_boundaries = Face.InternalBoundaries(flatFace)
|
|
1133
|
+
if len(internal_boundaries) == 0:
|
|
1134
|
+
return status
|
|
1135
|
+
|
|
1136
|
+
for ib in internal_boundaries:
|
|
1137
|
+
vertices = Wire.Vertices(ib)
|
|
1138
|
+
poly = []
|
|
1139
|
+
for v in vertices:
|
|
1140
|
+
poly.append([Vertex.X(v), Vertex.Y(v)])
|
|
1141
|
+
status2 = ray_tracing_method(Vertex.X(vertex), Vertex.Y(vertex), poly)
|
|
1142
|
+
if status2:
|
|
1143
|
+
return False
|
|
1144
|
+
return status
|
|
1145
|
+
|
|
1146
|
+
@staticmethod
|
|
1147
|
+
def MedialAxis(face: topologic.Face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
|
|
1148
|
+
"""
|
|
1149
|
+
Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.
|
|
1150
|
+
|
|
1151
|
+
Parameters
|
|
1152
|
+
----------
|
|
1153
|
+
face : topologic.Face
|
|
1154
|
+
The input face.
|
|
1155
|
+
resolution : int , optional
|
|
1156
|
+
The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
|
|
1157
|
+
externalVertices : bool , optional
|
|
1158
|
+
If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
|
|
1159
|
+
internalVertices : bool , optional
|
|
1160
|
+
If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
|
|
1161
|
+
toLeavesOnly : bool , optional
|
|
1162
|
+
If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
|
|
1163
|
+
angTolerance : float , optional
|
|
1164
|
+
The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
|
|
1165
|
+
tolerance : float , optional
|
|
1166
|
+
The desired tolerance. The default is 0.0001.
|
|
1167
|
+
|
|
1168
|
+
Returns
|
|
1169
|
+
-------
|
|
1170
|
+
topologic.Wire
|
|
1171
|
+
The medial axis of the input face.
|
|
1172
|
+
|
|
1173
|
+
"""
|
|
1174
|
+
from topologicpy.Vertex import Vertex
|
|
1175
|
+
from topologicpy.Edge import Edge
|
|
1176
|
+
from topologicpy.Wire import Wire
|
|
1177
|
+
from topologicpy.Shell import Shell
|
|
1178
|
+
from topologicpy.Cluster import Cluster
|
|
1179
|
+
from topologicpy.Topology import Topology
|
|
1180
|
+
from topologicpy.Dictionary import Dictionary
|
|
1181
|
+
|
|
1182
|
+
def touchesEdge(vertex,edges, tolerance=0.0001):
|
|
1183
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
1184
|
+
return False
|
|
1185
|
+
for edge in edges:
|
|
1186
|
+
u = Edge.ParameterAtVertex(edge, vertex, mantissa=4)
|
|
1187
|
+
if not u:
|
|
1188
|
+
continue
|
|
1189
|
+
if 0<u<1:
|
|
1190
|
+
return True
|
|
1191
|
+
return False
|
|
1192
|
+
|
|
1193
|
+
# Flatten the input face
|
|
1194
|
+
flatFace = Face.Flatten(face)
|
|
1195
|
+
# Retrieve the needed transformations
|
|
1196
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
1197
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
1198
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
1199
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
1200
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
1201
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
1202
|
+
|
|
1203
|
+
# Create a Vertex at the world's origin (0,0,0)
|
|
1204
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
1205
|
+
|
|
1206
|
+
faceVertices = Face.Vertices(flatFace)
|
|
1207
|
+
faceEdges = Face.Edges(flatFace)
|
|
1208
|
+
vertices = []
|
|
1209
|
+
resolution = 10 - resolution
|
|
1210
|
+
resolution = min(max(resolution, 1), 10)
|
|
1211
|
+
for e in faceEdges:
|
|
1212
|
+
for n in range(resolution, 100, resolution):
|
|
1213
|
+
vertices.append(Edge.VertexByParameter(e,n*0.01))
|
|
1214
|
+
|
|
1215
|
+
voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
|
|
1216
|
+
voronoiEdges = Shell.Edges(voronoi)
|
|
1217
|
+
|
|
1218
|
+
medialAxisEdges = []
|
|
1219
|
+
for e in voronoiEdges:
|
|
1220
|
+
sv = Edge.StartVertex(e)
|
|
1221
|
+
ev = Edge.EndVertex(e)
|
|
1222
|
+
svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
|
|
1223
|
+
evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
|
|
1224
|
+
#connectsToCorners = (Vertex.Index(sv, faceVertices) != None) or (Vertex.Index(ev, faceVertices) != None)
|
|
1225
|
+
#if Face.IsInside(flatFace, sv, tolerance=tolerance) and Face.IsInside(flatFace, ev, tolerance=tolerance):
|
|
1226
|
+
if not svTouchesEdge and not evTouchesEdge:
|
|
1227
|
+
medialAxisEdges.append(e)
|
|
1228
|
+
|
|
1229
|
+
extBoundary = Face.ExternalBoundary(flatFace)
|
|
1230
|
+
extVertices = Wire.Vertices(extBoundary)
|
|
1231
|
+
|
|
1232
|
+
intBoundaries = Face.InternalBoundaries(flatFace)
|
|
1233
|
+
intVertices = []
|
|
1234
|
+
for ib in intBoundaries:
|
|
1235
|
+
intVertices = intVertices+Wire.Vertices(ib)
|
|
1236
|
+
|
|
1237
|
+
theVertices = []
|
|
1238
|
+
if internalVertices:
|
|
1239
|
+
theVertices = theVertices+intVertices
|
|
1240
|
+
if externalVertices:
|
|
1241
|
+
theVertices = theVertices+extVertices
|
|
1242
|
+
|
|
1243
|
+
tempWire = Cluster.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
|
|
1244
|
+
if isinstance(tempWire, topologic.Wire) and angTolerance > 0:
|
|
1245
|
+
tempWire = Wire.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
|
|
1246
|
+
medialAxisEdges = Wire.Edges(tempWire)
|
|
1247
|
+
for v in theVertices:
|
|
1248
|
+
nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)
|
|
1249
|
+
|
|
1250
|
+
if isinstance(nv, topologic.Vertex):
|
|
1251
|
+
if toLeavesOnly:
|
|
1252
|
+
adjVertices = Topology.AdjacentTopologies(nv, tempWire)
|
|
1253
|
+
if len(adjVertices) < 2:
|
|
1254
|
+
medialAxisEdges.append(Edge.ByVertices([nv, v]))
|
|
1255
|
+
else:
|
|
1256
|
+
medialAxisEdges.append(Edge.ByVertices([nv, v]))
|
|
1257
|
+
medialAxis = Cluster.SelfMerge(Cluster.ByTopologies(medialAxisEdges))
|
|
1258
|
+
if isinstance(medialAxis, topologic.Wire) and angTolerance > 0:
|
|
1259
|
+
medialAxis = Wire.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
|
|
1260
|
+
medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
1261
|
+
medialAxis = Topology.Rotate(medialAxis, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
1262
|
+
medialAxis = Topology.Translate(medialAxis, xTran, yTran, zTran)
|
|
1263
|
+
return medialAxis
|
|
1264
|
+
|
|
1265
|
+
@staticmethod
|
|
1266
|
+
def Normal(face: topologic.Face, outputType: str = "xyz", mantissa: int = 4) -> list:
|
|
1267
|
+
"""
|
|
1268
|
+
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
|
|
1269
|
+
|
|
1270
|
+
Parameters
|
|
1271
|
+
----------
|
|
1272
|
+
face : topologic.Face
|
|
1273
|
+
The input face.
|
|
1274
|
+
outputType : string , optional
|
|
1275
|
+
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
|
|
1276
|
+
mantissa : int , optional
|
|
1277
|
+
The desired length of the mantissa. The default is 4.
|
|
1278
|
+
|
|
1279
|
+
Returns
|
|
1280
|
+
-------
|
|
1281
|
+
list
|
|
1282
|
+
The normal vector to the input face. This is computed at the approximate center of the face.
|
|
1283
|
+
|
|
1284
|
+
"""
|
|
1285
|
+
return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
|
|
1286
|
+
|
|
1287
|
+
@staticmethod
|
|
1288
|
+
def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 4) -> list:
|
|
1289
|
+
"""
|
|
1290
|
+
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
|
|
1291
|
+
|
|
1292
|
+
Parameters
|
|
1293
|
+
----------
|
|
1294
|
+
face : topologic.Face
|
|
1295
|
+
The input face.
|
|
1296
|
+
u : float , optional
|
|
1297
|
+
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
|
|
1298
|
+
v : float , optional
|
|
1299
|
+
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
|
|
1300
|
+
outputType : string , optional
|
|
1301
|
+
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
|
|
1302
|
+
mantissa : int , optional
|
|
1303
|
+
The desired length of the mantissa. The default is 4.
|
|
1304
|
+
|
|
1305
|
+
Returns
|
|
1306
|
+
-------
|
|
1307
|
+
list
|
|
1308
|
+
The normal vector to the input face.
|
|
1309
|
+
|
|
1310
|
+
"""
|
|
1311
|
+
returnResult = []
|
|
1312
|
+
try:
|
|
1313
|
+
coords = topologic.FaceUtility.NormalAtParameters(face, u, v)
|
|
1314
|
+
x = round(coords[0], mantissa)
|
|
1315
|
+
y = round(coords[1], mantissa)
|
|
1316
|
+
z = round(coords[2], mantissa)
|
|
1317
|
+
outputType = list(outputType.lower())
|
|
1318
|
+
for axis in outputType:
|
|
1319
|
+
if axis == "x":
|
|
1320
|
+
returnResult.append(x)
|
|
1321
|
+
elif axis == "y":
|
|
1322
|
+
returnResult.append(y)
|
|
1323
|
+
elif axis == "z":
|
|
1324
|
+
returnResult.append(z)
|
|
1325
|
+
except:
|
|
1326
|
+
returnResult = None
|
|
1327
|
+
return returnResult
|
|
1328
|
+
|
|
1329
|
+
@staticmethod
|
|
1330
|
+
def NormalEdge(face: topologic.Face, length: float = 1.0) -> topologic.Edge:
|
|
1331
|
+
"""
|
|
1332
|
+
Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
|
|
1333
|
+
|
|
1334
|
+
Parameters
|
|
1335
|
+
----------
|
|
1336
|
+
face : topologic.Face
|
|
1337
|
+
The input face.
|
|
1338
|
+
length : float , optional
|
|
1339
|
+
The desired length of the normal edge. The default is 1.
|
|
1340
|
+
|
|
1341
|
+
Returns
|
|
1342
|
+
-------
|
|
1343
|
+
topologic.Edge
|
|
1344
|
+
The created normal edge to the input face. This is computed at the approximate center of the face.
|
|
1345
|
+
|
|
1346
|
+
"""
|
|
1347
|
+
return Face.NormalEdgeAtParameters(face, u=0.5, v=0.5, length=length)
|
|
1348
|
+
|
|
1349
|
+
@staticmethod
|
|
1350
|
+
def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0) -> topologic.Edge:
|
|
1351
|
+
"""
|
|
1352
|
+
Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
|
|
1353
|
+
|
|
1354
|
+
Parameters
|
|
1355
|
+
----------
|
|
1356
|
+
face : topologic.Face
|
|
1357
|
+
The input face.
|
|
1358
|
+
u : float , optional
|
|
1359
|
+
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
|
|
1360
|
+
v : float , optional
|
|
1361
|
+
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
|
|
1362
|
+
length : float , optional
|
|
1363
|
+
The desired length of the normal edge. The default is 1.
|
|
1364
|
+
|
|
1365
|
+
Returns
|
|
1366
|
+
-------
|
|
1367
|
+
topologic.Edge
|
|
1368
|
+
The created normal edge to the input face. This is computed at the approximate center of the face.
|
|
1369
|
+
|
|
1370
|
+
"""
|
|
1371
|
+
from topologicpy.Edge import Edge
|
|
1372
|
+
from topologicpy.Topology import Topology
|
|
1373
|
+
if not isinstance(face, topologic.Face):
|
|
1374
|
+
return None
|
|
1375
|
+
sv = Face.VertexByParameters(face=face, u=u, v=v)
|
|
1376
|
+
vec = Face.NormalAtParameters(face, u=u, v=v)
|
|
1377
|
+
ev = Topology.TranslateByDirectionDistance(sv, vec, length)
|
|
1378
|
+
return Edge.ByVertices([sv, ev])
|
|
1379
|
+
|
|
1380
|
+
@staticmethod
|
|
1381
|
+
def Planarize(face: topologic.Face, origin: topologic.Vertex = None, direction: list = None) -> topologic.Face:
|
|
1382
|
+
"""
|
|
1383
|
+
Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.
|
|
1384
|
+
|
|
1385
|
+
Parameters
|
|
1386
|
+
----------
|
|
1387
|
+
face : topologic.Face
|
|
1388
|
+
The input face.
|
|
1389
|
+
origin : topologic.Vertex , optional
|
|
1390
|
+
The old location to use as the origin of the movement. If set to None, the center of mass of the input face is used. The default is None.
|
|
1391
|
+
direction : list , optional
|
|
1392
|
+
The direction, expressed as a list of [X,Y,Z] that signifies the direction of the face. If set to None, the normal at *u* 0.5 and *v* 0.5 is considered the direction of the face. The deafult is None.
|
|
1393
|
+
|
|
1394
|
+
Returns
|
|
1395
|
+
-------
|
|
1396
|
+
topologic.Face
|
|
1397
|
+
The planarized face.
|
|
1398
|
+
|
|
1399
|
+
"""
|
|
1400
|
+
|
|
1401
|
+
from topologicpy.Vertex import Vertex
|
|
1402
|
+
from topologicpy.Wire import Wire
|
|
1403
|
+
from topologicpy.Topology import Topology
|
|
1404
|
+
from topologicpy.Dictionary import Dictionary
|
|
1405
|
+
|
|
1406
|
+
if not isinstance(face, topologic.Face):
|
|
1407
|
+
return None
|
|
1408
|
+
if not isinstance(origin, topologic.Vertex):
|
|
1409
|
+
origin = Topology.CenterOfMass(face)
|
|
1410
|
+
if not isinstance(direction, list):
|
|
1411
|
+
direction = Face.NormalAtParameters(face, 0.5, 0.5)
|
|
1412
|
+
flatFace = Face.Flatten(face, oldLocation=origin, direction=direction)
|
|
1413
|
+
|
|
1414
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
1415
|
+
# Retrieve the needed transformations
|
|
1416
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
1417
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
1418
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
1419
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
1420
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
1421
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
1422
|
+
|
|
1423
|
+
planarizedFace = Topology.Rotate(flatFace, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
1424
|
+
planarizedFace = Topology.Rotate(planarizedFace, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
1425
|
+
planarizedFace = Topology.Translate(planarizedFace, xTran, yTran, zTran)
|
|
1426
|
+
return planarizedFace
|
|
1427
|
+
|
|
1428
|
+
@staticmethod
|
|
1429
|
+
def Project(faceA: topologic.Face, faceB: topologic.Face, direction : list = None, mantissa: int = 4) -> topologic.Face:
|
|
1430
|
+
"""
|
|
1431
|
+
Creates a projection of the first input face unto the second input face.
|
|
1432
|
+
|
|
1433
|
+
Parameters
|
|
1434
|
+
----------
|
|
1435
|
+
faceA : topologic.Face
|
|
1436
|
+
The face to be projected.
|
|
1437
|
+
faceB : topologic.Face
|
|
1438
|
+
The face unto which the first input face will be projected.
|
|
1439
|
+
direction : list, optional
|
|
1440
|
+
The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
|
|
1441
|
+
mantissa : int , optional
|
|
1442
|
+
The desired length of the mantissa. The default is 4.
|
|
1443
|
+
|
|
1444
|
+
Returns
|
|
1445
|
+
-------
|
|
1446
|
+
topologic.Face
|
|
1447
|
+
The projected Face.
|
|
1448
|
+
|
|
1449
|
+
"""
|
|
1450
|
+
|
|
1451
|
+
from topologicpy.Wire import Wire
|
|
1452
|
+
|
|
1453
|
+
if not faceA:
|
|
1454
|
+
return None
|
|
1455
|
+
if not isinstance(faceA, topologic.Face):
|
|
1456
|
+
return None
|
|
1457
|
+
if not faceB:
|
|
1458
|
+
return None
|
|
1459
|
+
if not isinstance(faceB, topologic.Face):
|
|
1460
|
+
return None
|
|
1461
|
+
|
|
1462
|
+
eb = faceA.ExternalBoundary()
|
|
1463
|
+
ib_list = []
|
|
1464
|
+
_ = faceA.InternalBoundaries(ib_list)
|
|
1465
|
+
p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa)
|
|
1466
|
+
p_ib_list = []
|
|
1467
|
+
for ib in ib_list:
|
|
1468
|
+
temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa)
|
|
1469
|
+
if temp_ib:
|
|
1470
|
+
p_ib_list.append(temp_ib)
|
|
1471
|
+
return Face.ByWires(p_eb, p_ib_list)
|
|
1472
|
+
|
|
1473
|
+
@staticmethod
|
|
1474
|
+
def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
|
1475
|
+
"""
|
|
1476
|
+
Creates a rectangle.
|
|
1477
|
+
|
|
1478
|
+
Parameters
|
|
1479
|
+
----------
|
|
1480
|
+
origin : topologic.Vertex, optional
|
|
1481
|
+
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
|
|
1482
|
+
width : float , optional
|
|
1483
|
+
The width of the rectangle. The default is 1.0.
|
|
1484
|
+
length : float , optional
|
|
1485
|
+
The length of the rectangle. The default is 1.0.
|
|
1486
|
+
direction : list , optional
|
|
1487
|
+
The vector representing the up direction of the rectangle. The default is [0,0,1].
|
|
1488
|
+
placement : str , optional
|
|
1489
|
+
The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
|
|
1490
|
+
tolerance : float , optional
|
|
1491
|
+
The desired tolerance. The default is 0.0001.
|
|
1492
|
+
|
|
1493
|
+
Returns
|
|
1494
|
+
-------
|
|
1495
|
+
topologic.Face
|
|
1496
|
+
The created face.
|
|
1497
|
+
|
|
1498
|
+
"""
|
|
1499
|
+
from topologicpy.Wire import Wire
|
|
1500
|
+
wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
|
|
1501
|
+
if not isinstance(wire, topologic.Wire):
|
|
1502
|
+
return None
|
|
1503
|
+
return Face.ByWire(wire)
|
|
1504
|
+
|
|
1505
|
+
@staticmethod
|
|
1506
|
+
def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
|
1507
|
+
"""
|
|
1508
|
+
Creates a square.
|
|
1509
|
+
|
|
1510
|
+
Parameters
|
|
1511
|
+
----------
|
|
1512
|
+
origin : topologic.Vertex , optional
|
|
1513
|
+
The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
|
|
1514
|
+
size : float , optional
|
|
1515
|
+
The size of the square. The default is 1.0.
|
|
1516
|
+
direction : list , optional
|
|
1517
|
+
The vector representing the up direction of the square. The default is [0,0,1].
|
|
1518
|
+
placement : str , optional
|
|
1519
|
+
The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
|
|
1520
|
+
tolerance : float , optional
|
|
1521
|
+
The desired tolerance. The default is 0.0001.
|
|
1522
|
+
|
|
1523
|
+
Returns
|
|
1524
|
+
-------
|
|
1525
|
+
topologic.Face
|
|
1526
|
+
The created square.
|
|
1527
|
+
|
|
1528
|
+
"""
|
|
1529
|
+
return Face.Rectangle(origin = origin, width = size, length = size, direction = direction, placement = placement, tolerance = tolerance)
|
|
1530
|
+
|
|
1531
|
+
@staticmethod
|
|
1532
|
+
def Star(origin: topologic.Vertex = None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
|
1533
|
+
"""
|
|
1534
|
+
Creates a star.
|
|
1535
|
+
|
|
1536
|
+
Parameters
|
|
1537
|
+
----------
|
|
1538
|
+
origin : topologic.Vertex, optional
|
|
1539
|
+
The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
|
|
1540
|
+
radiusA : float , optional
|
|
1541
|
+
The outer radius of the star. The default is 1.0.
|
|
1542
|
+
radiusB : float , optional
|
|
1543
|
+
The outer radius of the star. The default is 0.4.
|
|
1544
|
+
rays : int , optional
|
|
1545
|
+
The number of star rays. The default is 5.
|
|
1546
|
+
direction : list , optional
|
|
1547
|
+
The vector representing the up direction of the star. The default is [0,0,1].
|
|
1548
|
+
placement : str , optional
|
|
1549
|
+
The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
|
|
1550
|
+
tolerance : float , optional
|
|
1551
|
+
The desired tolerance. The default is 0.0001.
|
|
1552
|
+
|
|
1553
|
+
Returns
|
|
1554
|
+
-------
|
|
1555
|
+
topologic.Face
|
|
1556
|
+
The created face.
|
|
1557
|
+
|
|
1558
|
+
"""
|
|
1559
|
+
from topologicpy.Wire import Wire
|
|
1560
|
+
wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
|
|
1561
|
+
if not isinstance(wire, topologic.Wire):
|
|
1562
|
+
return None
|
|
1563
|
+
return Face.ByWire(wire)
|
|
1564
|
+
|
|
1565
|
+
@staticmethod
|
|
1566
|
+
def Trapezoid(origin: topologic.Vertex = None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
|
1567
|
+
"""
|
|
1568
|
+
Creates a trapezoid.
|
|
1569
|
+
|
|
1570
|
+
Parameters
|
|
1571
|
+
----------
|
|
1572
|
+
origin : topologic.Vertex, optional
|
|
1573
|
+
The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
|
|
1574
|
+
widthA : float , optional
|
|
1575
|
+
The width of the bottom edge of the trapezoid. The default is 1.0.
|
|
1576
|
+
widthB : float , optional
|
|
1577
|
+
The width of the top edge of the trapezoid. The default is 0.75.
|
|
1578
|
+
offsetA : float , optional
|
|
1579
|
+
The offset of the bottom edge of the trapezoid. The default is 0.0.
|
|
1580
|
+
offsetB : float , optional
|
|
1581
|
+
The offset of the top edge of the trapezoid. The default is 0.0.
|
|
1582
|
+
length : float , optional
|
|
1583
|
+
The length of the trapezoid. The default is 1.0.
|
|
1584
|
+
direction : list , optional
|
|
1585
|
+
The vector representing the up direction of the trapezoid. The default is [0,0,1].
|
|
1586
|
+
placement : str , optional
|
|
1587
|
+
The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
1588
|
+
tolerance : float , optional
|
|
1589
|
+
The desired tolerance. The default is 0.0001.
|
|
1590
|
+
|
|
1591
|
+
Returns
|
|
1592
|
+
-------
|
|
1593
|
+
topologic.Face
|
|
1594
|
+
The created trapezoid.
|
|
1595
|
+
|
|
1596
|
+
"""
|
|
1597
|
+
from topologicpy.Wire import Wire
|
|
1598
|
+
wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
|
|
1599
|
+
if not isinstance(wire, topologic.Wire):
|
|
1600
|
+
return None
|
|
1601
|
+
return Face.ByWire(wire)
|
|
1602
|
+
|
|
1603
|
+
@staticmethod
|
|
1604
|
+
def Triangulate(face:topologic.Face) -> list:
|
|
1605
|
+
"""
|
|
1606
|
+
Triangulates the input face and returns a list of faces.
|
|
1607
|
+
|
|
1608
|
+
Parameters
|
|
1609
|
+
----------
|
|
1610
|
+
face : topologic.Face
|
|
1611
|
+
The input face.
|
|
1612
|
+
|
|
1613
|
+
Returns
|
|
1614
|
+
-------
|
|
1615
|
+
list
|
|
1616
|
+
The list of triangles of the input face.
|
|
1617
|
+
|
|
1618
|
+
"""
|
|
1619
|
+
from topologicpy.Vertex import Vertex
|
|
1620
|
+
from topologicpy.Wire import Wire
|
|
1621
|
+
from topologicpy.Topology import Topology
|
|
1622
|
+
from topologicpy.Dictionary import Dictionary
|
|
1623
|
+
|
|
1624
|
+
if not isinstance(face, topologic.Face):
|
|
1625
|
+
return None
|
|
1626
|
+
flatFace = Face.Flatten(face)
|
|
1627
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
1628
|
+
# Retrieve the needed transformations
|
|
1629
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
1630
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
1631
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
1632
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
1633
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
1634
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
1635
|
+
|
|
1636
|
+
faceTriangles = []
|
|
1637
|
+
for i in range(0,5,1):
|
|
1638
|
+
try:
|
|
1639
|
+
_ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, faceTriangles)
|
|
1640
|
+
break
|
|
1641
|
+
except:
|
|
1642
|
+
continue
|
|
1643
|
+
if len(faceTriangles) < 1:
|
|
1644
|
+
return [face]
|
|
1645
|
+
finalFaces = []
|
|
1646
|
+
for f in faceTriangles:
|
|
1647
|
+
f = Topology.Rotate(f, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
1648
|
+
f = Topology.Rotate(f, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
1649
|
+
f = Topology.Translate(f, xTran, yTran, zTran)
|
|
1650
|
+
if Face.Angle(face, f) > 90:
|
|
1651
|
+
wire = Face.ExternalBoundary(f)
|
|
1652
|
+
wire = Wire.Invert(wire)
|
|
1653
|
+
f = topologic.Face.ByExternalBoundary(wire)
|
|
1654
|
+
finalFaces.append(f)
|
|
1655
|
+
else:
|
|
1656
|
+
finalFaces.append(f)
|
|
1657
|
+
return finalFaces
|
|
1658
|
+
|
|
1659
|
+
@staticmethod
|
|
1660
|
+
def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) -> topologic.Face:
|
|
1661
|
+
"""
|
|
1662
|
+
Trims the input face by the input wire.
|
|
1663
|
+
|
|
1664
|
+
Parameters
|
|
1665
|
+
----------
|
|
1666
|
+
face : topologic.Face
|
|
1667
|
+
The input face.
|
|
1668
|
+
wire : topologic.Wire
|
|
1669
|
+
The input wire.
|
|
1670
|
+
reverse : bool , optional
|
|
1671
|
+
If set to True, the effect of the trim will be reversed. The default is False.
|
|
1672
|
+
|
|
1673
|
+
Returns
|
|
1674
|
+
-------
|
|
1675
|
+
topologic.Face
|
|
1676
|
+
The resulting trimmed face.
|
|
1677
|
+
|
|
1678
|
+
"""
|
|
1679
|
+
if not isinstance(face, topologic.Face):
|
|
1680
|
+
return None
|
|
1681
|
+
if not isinstance(wire, topologic.Wire):
|
|
1682
|
+
return face
|
|
1683
|
+
trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False)
|
|
1684
|
+
if reverse:
|
|
1685
|
+
trimmed_face = face.Difference(trimmed_face)
|
|
1686
|
+
return trimmed_face
|
|
1687
|
+
|
|
1688
|
+
@staticmethod
|
|
1689
|
+
def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) -> topologic.Vertex:
|
|
1690
|
+
"""
|
|
1691
|
+
Creates a vertex at the *u* and *v* parameters of the input face.
|
|
1692
|
+
|
|
1693
|
+
Parameters
|
|
1694
|
+
----------
|
|
1695
|
+
face : topologic.Face
|
|
1696
|
+
The input face.
|
|
1697
|
+
u : float , optional
|
|
1698
|
+
The *u* parameter of the input face. The default is 0.5.
|
|
1699
|
+
v : float , optional
|
|
1700
|
+
The *v* parameter of the input face. The default is 0.5.
|
|
1701
|
+
|
|
1702
|
+
Returns
|
|
1703
|
+
-------
|
|
1704
|
+
vertex : topologic vertex
|
|
1705
|
+
The created vertex.
|
|
1706
|
+
|
|
1707
|
+
"""
|
|
1708
|
+
if not isinstance(face, topologic.Face):
|
|
1709
|
+
return None
|
|
1710
|
+
return topologic.FaceUtility.VertexAtParameters(face, u, v)
|
|
1711
|
+
|
|
1712
|
+
@staticmethod
|
|
1713
|
+
def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = "uv", mantissa: int = 4) -> list:
|
|
1714
|
+
"""
|
|
1715
|
+
Returns the *u* and *v* parameters of the input face at the location of the input vertex.
|
|
1716
|
+
|
|
1717
|
+
Parameters
|
|
1718
|
+
----------
|
|
1719
|
+
face : topologic.Face
|
|
1720
|
+
The input face.
|
|
1721
|
+
vertex : topologic.Vertex
|
|
1722
|
+
The input vertex.
|
|
1723
|
+
outputType : string , optional
|
|
1724
|
+
The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
|
|
1725
|
+
mantissa : int , optional
|
|
1726
|
+
The desired length of the mantissa. The default is 4.
|
|
1727
|
+
|
|
1728
|
+
Returns
|
|
1729
|
+
-------
|
|
1730
|
+
list
|
|
1731
|
+
The list of *u* and/or *v* as specified by the outputType input.
|
|
1732
|
+
|
|
1733
|
+
"""
|
|
1734
|
+
if not isinstance(face, topologic.Face):
|
|
1735
|
+
return None
|
|
1736
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
1737
|
+
return None
|
|
1738
|
+
params = topologic.FaceUtility.ParametersAtVertex(face, vertex)
|
|
1739
|
+
u = round(params[0], mantissa)
|
|
1740
|
+
v = round(params[1], mantissa)
|
|
1741
|
+
outputType = list(outputType.lower())
|
|
1742
|
+
returnResult = []
|
|
1743
|
+
for param in outputType:
|
|
1744
|
+
if param == "u":
|
|
1745
|
+
returnResult.append(u)
|
|
1746
|
+
elif param == "v":
|
|
1747
|
+
returnResult.append(v)
|
|
1748
|
+
return returnResult
|
|
1749
|
+
|
|
1750
|
+
@staticmethod
|
|
1751
|
+
def Vertices(face: topologic.Face) -> list:
|
|
1752
|
+
"""
|
|
1753
|
+
Returns the vertices of the input face.
|
|
1754
|
+
|
|
1755
|
+
Parameters
|
|
1756
|
+
----------
|
|
1757
|
+
face : topologic.Face
|
|
1758
|
+
The input face.
|
|
1759
|
+
|
|
1760
|
+
Returns
|
|
1761
|
+
-------
|
|
1762
|
+
list
|
|
1763
|
+
The list of vertices.
|
|
1764
|
+
|
|
1765
|
+
"""
|
|
1766
|
+
if not isinstance(face, topologic.Face):
|
|
1767
|
+
return None
|
|
1768
|
+
vertices = []
|
|
1769
|
+
_ = face.Vertices(None, vertices)
|
|
1770
|
+
return vertices
|
|
1771
|
+
|
|
1772
|
+
@staticmethod
|
|
1773
|
+
def Wire(face: topologic.Face) -> topologic.Wire:
|
|
1774
|
+
"""
|
|
1775
|
+
Returns the external boundary (closed wire) of the input face.
|
|
1776
|
+
|
|
1777
|
+
Parameters
|
|
1778
|
+
----------
|
|
1779
|
+
face : topologic.Face
|
|
1780
|
+
The input face.
|
|
1781
|
+
|
|
1782
|
+
Returns
|
|
1783
|
+
-------
|
|
1784
|
+
topologic.Wire
|
|
1785
|
+
The external boundary of the input face.
|
|
1786
|
+
|
|
1787
|
+
"""
|
|
1788
|
+
return face.ExternalBoundary()
|
|
1789
|
+
|
|
1790
|
+
@staticmethod
|
|
1791
|
+
def Wires(face: topologic.Face) -> list:
|
|
1792
|
+
"""
|
|
1793
|
+
Returns the wires of the input face.
|
|
1794
|
+
|
|
1795
|
+
Parameters
|
|
1796
|
+
----------
|
|
1797
|
+
face : topologic.Face
|
|
1798
|
+
The input face.
|
|
1799
|
+
|
|
1800
|
+
Returns
|
|
1801
|
+
-------
|
|
1802
|
+
list
|
|
1803
|
+
The list of wires.
|
|
1804
|
+
|
|
1805
|
+
"""
|
|
1806
|
+
if not isinstance(face, topologic.Face):
|
|
1807
|
+
return None
|
|
1808
|
+
wires = []
|
|
1809
|
+
_ = face.Wires(None, wires)
|
|
1810
|
+
return wires
|