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/Face.py
CHANGED
@@ -1,2141 +1,2141 @@
|
|
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.Vector import Vector
|
19
|
-
from topologicpy.Wire import Wire
|
20
|
-
from topologicpy.Topology import Topology
|
21
|
-
import math
|
22
|
-
import os
|
23
|
-
import warnings
|
24
|
-
|
25
|
-
try:
|
26
|
-
import numpy as np
|
27
|
-
except:
|
28
|
-
print("Face - Installing required numpy library.")
|
29
|
-
try:
|
30
|
-
os.system("pip install numpy")
|
31
|
-
except:
|
32
|
-
os.system("pip install numpy --user")
|
33
|
-
try:
|
34
|
-
import numpy as np
|
35
|
-
print("Face - numpy library installed correctly.")
|
36
|
-
except:
|
37
|
-
warnings.warn("Face - Error: Could not import numpy.")
|
38
|
-
|
39
|
-
class Face(Topology):
|
40
|
-
@staticmethod
|
41
|
-
def AddInternalBoundaries(face: topologic.Face, wires: list) -> topologic.Face:
|
42
|
-
"""
|
43
|
-
Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
|
44
|
-
|
45
|
-
Parameters
|
46
|
-
----------
|
47
|
-
face : topologic.Face
|
48
|
-
The input face.
|
49
|
-
wires : list
|
50
|
-
The input list of internal boundaries (closed wires).
|
51
|
-
|
52
|
-
Returns
|
53
|
-
-------
|
54
|
-
topologic.Face
|
55
|
-
The created face with internal boundaries added to it.
|
56
|
-
|
57
|
-
"""
|
58
|
-
|
59
|
-
if not isinstance(face, topologic.Face):
|
60
|
-
print("Face.AddInternalBoundaries - Error: The input face parameter is not a valid topologic face. Returning None.")
|
61
|
-
return None
|
62
|
-
if not isinstance(wires, list):
|
63
|
-
print("Face.AddInternalBoundaries - Warning: The input wires parameter is not a valid list. Returning the input face.")
|
64
|
-
return face
|
65
|
-
wireList = [w for w in wires if isinstance(w, topologic.Wire)]
|
66
|
-
if len(wireList) < 1:
|
67
|
-
print("Face.AddInternalBoundaries - Warning: The input wires parameter does not contain any valid wires. Returning the input face.")
|
68
|
-
return face
|
69
|
-
faceeb = face.ExternalBoundary()
|
70
|
-
faceibList = []
|
71
|
-
_ = face.InternalBoundaries(faceibList)
|
72
|
-
for wire in wires:
|
73
|
-
faceibList.append(wire)
|
74
|
-
return topologic.Face.ByExternalInternalBoundaries(faceeb, faceibList)
|
75
|
-
|
76
|
-
@staticmethod
|
77
|
-
def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) -> topologic.Face:
|
78
|
-
"""
|
79
|
-
Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
|
80
|
-
|
81
|
-
Parameters
|
82
|
-
----------
|
83
|
-
face : topologic.Face
|
84
|
-
The input face.
|
85
|
-
cluster : topologic.Cluster
|
86
|
-
The input cluster of internal boundaries (topologic wires).
|
87
|
-
|
88
|
-
Returns
|
89
|
-
-------
|
90
|
-
topologic.Face
|
91
|
-
The created face with internal boundaries added to it.
|
92
|
-
|
93
|
-
"""
|
94
|
-
if not isinstance(face, topologic.Face):
|
95
|
-
print("Face.AddInternalBoundariesCluster - Warning: The input cluster parameter is not a valid cluster. Returning None.")
|
96
|
-
return None
|
97
|
-
if not cluster:
|
98
|
-
return face
|
99
|
-
if not isinstance(cluster, topologic.Cluster):
|
100
|
-
return face
|
101
|
-
wires = []
|
102
|
-
_ = cluster.Wires(None, wires)
|
103
|
-
return Face.AddInternalBoundaries(face, wires)
|
104
|
-
|
105
|
-
@staticmethod
|
106
|
-
def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 6) -> float:
|
107
|
-
"""
|
108
|
-
Returns the angle in degrees between the two input faces.
|
109
|
-
|
110
|
-
Parameters
|
111
|
-
----------
|
112
|
-
faceA : topologic.Face
|
113
|
-
The first input face.
|
114
|
-
faceB : topologic.Face
|
115
|
-
The second input face.
|
116
|
-
mantissa : int , optional
|
117
|
-
The desired length of the mantissa. The default is 6.
|
118
|
-
|
119
|
-
Returns
|
120
|
-
-------
|
121
|
-
float
|
122
|
-
The angle in degrees between the two input faces.
|
123
|
-
|
124
|
-
"""
|
125
|
-
from topologicpy.Vector import Vector
|
126
|
-
if not isinstance(faceA, topologic.Face):
|
127
|
-
print("Face.Angle - Warning: The input faceA parameter is not a valid topologic face. Returning None.")
|
128
|
-
return None
|
129
|
-
if not isinstance(faceB, topologic.Face):
|
130
|
-
print("Face.Angle - Warning: The input faceB parameter is not a valid topologic face. Returning None.")
|
131
|
-
return None
|
132
|
-
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
|
133
|
-
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
|
134
|
-
return round((Vector.Angle(dirA, dirB)), mantissa)
|
135
|
-
|
136
|
-
@staticmethod
|
137
|
-
def Area(face: topologic.Face, mantissa: int = 6) -> float:
|
138
|
-
"""
|
139
|
-
Returns the area of the input face.
|
140
|
-
|
141
|
-
Parameters
|
142
|
-
----------
|
143
|
-
face : topologic.Face
|
144
|
-
The input face.
|
145
|
-
mantissa : int , optional
|
146
|
-
The desired length of the mantissa. The default is 6.
|
147
|
-
|
148
|
-
Returns
|
149
|
-
-------
|
150
|
-
float
|
151
|
-
The area of the input face.
|
152
|
-
|
153
|
-
"""
|
154
|
-
if not isinstance(face, topologic.Face):
|
155
|
-
print("Face.Area - Warning: The input face parameter is not a valid topologic face. Returning None.")
|
156
|
-
return None
|
157
|
-
area = None
|
158
|
-
try:
|
159
|
-
area = round(topologic.FaceUtility.Area(face), mantissa)
|
160
|
-
except:
|
161
|
-
area = None
|
162
|
-
return area
|
163
|
-
|
164
|
-
@staticmethod
|
165
|
-
def BoundingRectangle(topology: topologic.Topology, optimize: int = 0, tolerance: float = 0.0001) -> topologic.Face:
|
166
|
-
"""
|
167
|
-
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.
|
168
|
-
|
169
|
-
Parameters
|
170
|
-
----------
|
171
|
-
topology : topologic.Topology
|
172
|
-
The input topology.
|
173
|
-
optimize : int , optional
|
174
|
-
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.
|
175
|
-
tolerance : float , optional
|
176
|
-
The desired tolerance. The default is 0.0001.
|
177
|
-
|
178
|
-
Returns
|
179
|
-
-------
|
180
|
-
topologic.Face
|
181
|
-
The bounding rectangle of the input topology.
|
182
|
-
|
183
|
-
"""
|
184
|
-
from topologicpy.Wire import Wire
|
185
|
-
from topologicpy.Face import Face
|
186
|
-
from topologicpy.Cluster import Cluster
|
187
|
-
from topologicpy.Topology import Topology
|
188
|
-
from topologicpy.Dictionary import Dictionary
|
189
|
-
def bb(topology):
|
190
|
-
vertices = []
|
191
|
-
_ = topology.Vertices(None, vertices)
|
192
|
-
x = []
|
193
|
-
y = []
|
194
|
-
for aVertex in vertices:
|
195
|
-
x.append(aVertex.X())
|
196
|
-
y.append(aVertex.Y())
|
197
|
-
minX = min(x)
|
198
|
-
minY = min(y)
|
199
|
-
maxX = max(x)
|
200
|
-
maxY = max(y)
|
201
|
-
return [minX, minY, maxX, maxY]
|
202
|
-
|
203
|
-
if not isinstance(topology, topologic.Topology):
|
204
|
-
print("Face.BoundingRectangle - Warning: The input topology parameter is not a valid topologic topology. Returning None.")
|
205
|
-
return None
|
206
|
-
vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
|
207
|
-
topology = Cluster.ByTopologies(vertices)
|
208
|
-
boundingBox = bb(topology)
|
209
|
-
minX = boundingBox[0]
|
210
|
-
minY = boundingBox[1]
|
211
|
-
maxX = boundingBox[2]
|
212
|
-
maxY = boundingBox[3]
|
213
|
-
w = abs(maxX - minX)
|
214
|
-
l = abs(maxY - minY)
|
215
|
-
best_area = l*w
|
216
|
-
orig_area = best_area
|
217
|
-
best_z = 0
|
218
|
-
best_bb = boundingBox
|
219
|
-
origin = Topology.Centroid(topology)
|
220
|
-
optimize = min(max(optimize, 0), 10)
|
221
|
-
if optimize > 0:
|
222
|
-
factor = (round(((11 - optimize)/30 + 0.57), 2))
|
223
|
-
flag = False
|
224
|
-
for n in range(10,0,-1):
|
225
|
-
if flag:
|
226
|
-
break
|
227
|
-
za = n
|
228
|
-
zb = 90+n
|
229
|
-
zc = n
|
230
|
-
for z in range(za,zb,zc):
|
231
|
-
if flag:
|
232
|
-
break
|
233
|
-
t = Topology.Rotate(topology, origin=origin, axis=[0, 0, 1], angle=z)
|
234
|
-
minX, minY, maxX, maxY = bb(t)
|
235
|
-
w = abs(maxX - minX)
|
236
|
-
l = abs(maxY - minY)
|
237
|
-
area = l*w
|
238
|
-
if area < orig_area*factor:
|
239
|
-
best_area = area
|
240
|
-
best_z = z
|
241
|
-
best_bb = [minX, minY, maxX, maxY]
|
242
|
-
flag = True
|
243
|
-
break
|
244
|
-
if area < best_area:
|
245
|
-
best_area = area
|
246
|
-
best_z = z
|
247
|
-
best_bb = [minX, minY, maxX, maxY]
|
248
|
-
|
249
|
-
else:
|
250
|
-
best_bb = boundingBox
|
251
|
-
|
252
|
-
minX, minY, maxX, maxY = best_bb
|
253
|
-
vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
|
254
|
-
vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
|
255
|
-
vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
|
256
|
-
vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)
|
257
|
-
|
258
|
-
baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
|
259
|
-
baseFace = Face.ByWire(baseWire, tolerance=tolerance)
|
260
|
-
baseFace = Topology.Rotate(baseFace, origin=origin, axis=[0, 0, 1], angle=-best_z)
|
261
|
-
dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
|
262
|
-
baseFace = Topology.SetDictionary(baseFace, dictionary)
|
263
|
-
return baseFace
|
264
|
-
|
265
|
-
@staticmethod
|
266
|
-
def ByEdges(edges: list, tolerance : float = 0.0001) -> topologic.Face:
|
267
|
-
"""
|
268
|
-
Creates a face from the input list of edges.
|
269
|
-
|
270
|
-
Parameters
|
271
|
-
----------
|
272
|
-
edges : list
|
273
|
-
The input list of edges.
|
274
|
-
tolerance : float , optional
|
275
|
-
The desired tolerance. The default is 0.0001.
|
276
|
-
|
277
|
-
Returns
|
278
|
-
-------
|
279
|
-
face : topologic.Face
|
280
|
-
The created face.
|
281
|
-
|
282
|
-
"""
|
283
|
-
from topologicpy.Wire import Wire
|
284
|
-
if not isinstance(edges, list):
|
285
|
-
print("Face.ByEdges - Error: The input edges parameter is not a valid list. Returning None.")
|
286
|
-
return None
|
287
|
-
edges = [e for e in edges if isinstance(e, topologic.Edge)]
|
288
|
-
if len(edges) < 1:
|
289
|
-
print("Face.ByEdges - Error: The input edges parameter does not contain any valid edges. Returning None.")
|
290
|
-
return None
|
291
|
-
wire = Wire.ByEdges(edges, tolerance=tolerance)
|
292
|
-
if not isinstance(wire, topologic.Wire):
|
293
|
-
print("Face.ByEdges - Error: Could not create the required wire. Returning None.")
|
294
|
-
return None
|
295
|
-
return Face.ByWire(wire, tolerance=tolerance)
|
296
|
-
|
297
|
-
@staticmethod
|
298
|
-
def ByEdgesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Face:
|
299
|
-
"""
|
300
|
-
Creates a face from the input cluster of edges.
|
301
|
-
|
302
|
-
Parameters
|
303
|
-
----------
|
304
|
-
cluster : topologic.Cluster
|
305
|
-
The input cluster of edges.
|
306
|
-
tolerance : float , optional
|
307
|
-
The desired tolerance. The default is 0.0001.
|
308
|
-
|
309
|
-
Returns
|
310
|
-
-------
|
311
|
-
face : topologic.Face
|
312
|
-
The created face.
|
313
|
-
|
314
|
-
"""
|
315
|
-
from topologicpy.Cluster import Cluster
|
316
|
-
if not isinstance(cluster, topologic.Cluster):
|
317
|
-
print("Face.ByEdgesCluster - Warning: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
318
|
-
return None
|
319
|
-
edges = Cluster.Edges(cluster)
|
320
|
-
if len(edges) < 1:
|
321
|
-
print("Face.ByEdgesCluster - Warning: The input cluster parameter does not contain any valid edges. Returning None.")
|
322
|
-
return None
|
323
|
-
return Face.ByEdges(edges, tolerance=tolerance)
|
324
|
-
|
325
|
-
@staticmethod
|
326
|
-
def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False,
|
327
|
-
miterThreshold: float = None, offsetKey: str = None,
|
328
|
-
miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001) -> topologic.Face:
|
329
|
-
"""
|
330
|
-
Creates an offset face from the input face.
|
331
|
-
|
332
|
-
Parameters
|
333
|
-
----------
|
334
|
-
face : topologic.Face
|
335
|
-
The input face.
|
336
|
-
offset : float , optional
|
337
|
-
The desired offset distance. The default is 1.0.
|
338
|
-
miter : bool , optional
|
339
|
-
if set to True, the corners will be mitered. The default is False.
|
340
|
-
miterThreshold : float , optional
|
341
|
-
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.
|
342
|
-
offsetKey : str , optional
|
343
|
-
If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
|
344
|
-
miterThresholdKey : str , optional
|
345
|
-
If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
|
346
|
-
step : bool , optional
|
347
|
-
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.
|
348
|
-
tolerance : float , optional
|
349
|
-
The desired tolerance. The default is 0.0001.
|
350
|
-
|
351
|
-
Returns
|
352
|
-
-------
|
353
|
-
topologic.Face
|
354
|
-
The created face.
|
355
|
-
|
356
|
-
"""
|
357
|
-
from topologicpy.Wire import Wire
|
358
|
-
if not isinstance(face, topologic.Face):
|
359
|
-
print("Face.ByOffset - Warning: The input face parameter is not a valid toplogic face. Returning None.")
|
360
|
-
return None
|
361
|
-
eb = Face.Wire(face)
|
362
|
-
internal_boundaries = Face.InternalBoundaries(face)
|
363
|
-
offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
|
364
|
-
offset_internal_boundaries = []
|
365
|
-
for internal_boundary in internal_boundaries:
|
366
|
-
offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
|
367
|
-
return Face.ByWires(offset_external_boundary, offset_internal_boundaries, tolerance=tolerance)
|
368
|
-
|
369
|
-
@staticmethod
|
370
|
-
def ByShell(shell: topologic.Shell, origin: topologic.Vertex = None, angTolerance: float = 0.1, tolerance: float = 0.0001)-> topologic.Face:
|
371
|
-
"""
|
372
|
-
Creates a face by merging the faces of the input shell.
|
373
|
-
|
374
|
-
Parameters
|
375
|
-
----------
|
376
|
-
shell : topologic.Shell
|
377
|
-
The input shell.
|
378
|
-
angTolerance : float , optional
|
379
|
-
The desired angular tolerance. The default is 0.1.
|
380
|
-
tolerance : float , optional
|
381
|
-
The desired tolerance. The default is 0.0001.
|
382
|
-
|
383
|
-
Returns
|
384
|
-
-------
|
385
|
-
topologic.Face
|
386
|
-
The created face.
|
387
|
-
|
388
|
-
"""
|
389
|
-
from topologicpy.Vertex import Vertex
|
390
|
-
from topologicpy.Wire import Wire
|
391
|
-
from topologicpy.Shell import Shell
|
392
|
-
from topologicpy.Cluster import Cluster
|
393
|
-
from topologicpy.Topology import Topology
|
394
|
-
from topologicpy.Dictionary import Dictionary
|
395
|
-
from topologicpy.Helper import Helper
|
396
|
-
|
397
|
-
def planarizeList(wireList):
|
398
|
-
returnList = []
|
399
|
-
for aWire in wireList:
|
400
|
-
returnList.append(Wire.Planarize(aWire))
|
401
|
-
return returnList
|
402
|
-
|
403
|
-
if not isinstance(shell, topologic.Shell):
|
404
|
-
print("Face.ByShell - Error: The input shell parameter is not a valid toplogic shell. Returning None.")
|
405
|
-
return None
|
406
|
-
|
407
|
-
if origin == None:
|
408
|
-
origin = Topology.Centroid(shell)
|
409
|
-
if not isinstance(origin, topologic.Vertex):
|
410
|
-
print("Face.ByShell - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
|
411
|
-
return None
|
412
|
-
|
413
|
-
# Try the simple method first
|
414
|
-
face = None
|
415
|
-
ext_boundary = Wire.RemoveCollinearEdges(Shell.ExternalBoundary(shell))
|
416
|
-
if isinstance(ext_boundary, topologic.Wire):
|
417
|
-
face = Face.ByWire(ext_boundary)
|
418
|
-
elif isinstance(ext_boundary, topologic.Cluster):
|
419
|
-
wires = Topology.Wires(ext_boundary)
|
420
|
-
faces = [Face.ByWire(w) for w in wires]
|
421
|
-
areas = [Face.Area(f) for f in faces]
|
422
|
-
wires = Helper.Sort(wires, areas, reverseFlags=[True])
|
423
|
-
face = Face.ByWires(wires[0], wires[1:])
|
424
|
-
|
425
|
-
if isinstance(face, topologic.Face):
|
426
|
-
return face
|
427
|
-
world_origin = Vertex.Origin()
|
428
|
-
planar_shell = Shell.Planarize(shell)
|
429
|
-
normal = Face.Normal(Topology.Faces(planar_shell)[0])
|
430
|
-
planar_shell = Topology.Flatten(planar_shell, origin=origin, direction=normal)
|
431
|
-
vertices = Shell.Vertices(planar_shell)
|
432
|
-
new_vertices = []
|
433
|
-
for v in vertices:
|
434
|
-
x, y, z = Vertex.Coordinates(v)
|
435
|
-
new_v = Vertex.ByCoordinates(x,y,0)
|
436
|
-
new_vertices.append(new_v)
|
437
|
-
planar_shell = Topology.SelfMerge(Topology.ReplaceVertices(planar_shell, verticesA=vertices, verticesB=new_vertices), tolerance=tolerance)
|
438
|
-
ext_boundary = Shell.ExternalBoundary(planar_shell, tolerance=tolerance)
|
439
|
-
ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
|
440
|
-
if not isinstance(ext_boundary, topologic.Topology):
|
441
|
-
print("Face.ByShell - Error: Could not derive the external boundary of the input shell parameter. Returning None.")
|
442
|
-
return None
|
443
|
-
|
444
|
-
if isinstance(ext_boundary, topologic.Wire):
|
445
|
-
if not Topology.IsPlanar(ext_boundary, tolerance=tolerance):
|
446
|
-
ext_boundary = Wire.Planarize(ext_boundary, origin=origin, tolerance=tolerance)
|
447
|
-
ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
|
448
|
-
try:
|
449
|
-
face = Face.ByWire(ext_boundary)
|
450
|
-
face = Topology.Unflatten(face, origin=origin, direction=normal)
|
451
|
-
return face
|
452
|
-
except:
|
453
|
-
print("Face.ByShell - Error: The operation failed. Returning None.")
|
454
|
-
return None
|
455
|
-
elif isinstance(ext_boundary, topologic.Cluster): # The shell has holes.
|
456
|
-
wires = []
|
457
|
-
_ = ext_boundary.Wires(None, wires)
|
458
|
-
faces = []
|
459
|
-
areas = []
|
460
|
-
for wire in wires:
|
461
|
-
aFace = Face.ByWire(wire, tolerance=tolerance)
|
462
|
-
if not isinstance(aFace, topologic.Face):
|
463
|
-
print("Face.ByShell - Error: The operation failed. Returning None.")
|
464
|
-
return None
|
465
|
-
anArea = abs(Face.Area(aFace))
|
466
|
-
faces.append(aFace)
|
467
|
-
areas.append(anArea)
|
468
|
-
max_index = areas.index(max(areas))
|
469
|
-
ext_boundary = faces[max_index]
|
470
|
-
int_boundaries = list(set(faces) - set([ext_boundary]))
|
471
|
-
int_wires = []
|
472
|
-
for int_boundary in int_boundaries:
|
473
|
-
temp_wires = []
|
474
|
-
_ = int_boundary.Wires(None, temp_wires)
|
475
|
-
int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
|
476
|
-
temp_wires = []
|
477
|
-
_ = ext_boundary.Wires(None, temp_wires)
|
478
|
-
ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
|
479
|
-
face = Face.ByWires(ext_wire, int_wires)
|
480
|
-
|
481
|
-
face = Topology.Unflatten(face, origin=origin, direction=normal)
|
482
|
-
return face
|
483
|
-
else:
|
484
|
-
return None
|
485
|
-
|
486
|
-
@staticmethod
|
487
|
-
def ByVertices(vertices: list, tolerance: float = 0.0001) -> topologic.Face:
|
488
|
-
|
489
|
-
"""
|
490
|
-
Creates a face from the input list of vertices.
|
491
|
-
|
492
|
-
Parameters
|
493
|
-
----------
|
494
|
-
vertices : list
|
495
|
-
The input list of vertices.
|
496
|
-
tolerance : float , optional
|
497
|
-
The desired tolerance. The default is 0.0001.
|
498
|
-
|
499
|
-
Returns
|
500
|
-
-------
|
501
|
-
topologic.Face
|
502
|
-
The created face.
|
503
|
-
|
504
|
-
"""
|
505
|
-
from topologicpy.Topology import Topology
|
506
|
-
from topologicpy.Wire import Wire
|
507
|
-
|
508
|
-
if not isinstance(vertices, list):
|
509
|
-
return None
|
510
|
-
vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
511
|
-
if len(vertexList) < 3:
|
512
|
-
return None
|
513
|
-
|
514
|
-
w = Wire.ByVertices(vertexList, tolerance=tolerance)
|
515
|
-
f = Face.ByWire(w, tolerance=tolerance)
|
516
|
-
return f
|
517
|
-
|
518
|
-
@staticmethod
|
519
|
-
def ByVerticesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Face:
|
520
|
-
"""
|
521
|
-
Creates a face from the input cluster of vertices.
|
522
|
-
|
523
|
-
Parameters
|
524
|
-
----------
|
525
|
-
cluster : topologic.Cluster
|
526
|
-
The input cluster of vertices.
|
527
|
-
tolerance : float , optional
|
528
|
-
The desired tolerance. The default is 0.0001.
|
529
|
-
|
530
|
-
Returns
|
531
|
-
-------
|
532
|
-
topologic.Face
|
533
|
-
The crearted face.
|
534
|
-
|
535
|
-
"""
|
536
|
-
from topologicpy.Cluster import Cluster
|
537
|
-
if not isinstance(cluster, topologic.Cluster):
|
538
|
-
return None
|
539
|
-
vertices = Cluster.Vertices(cluster)
|
540
|
-
return Face.ByVertices(vertices, tolerance=tolerance)
|
541
|
-
|
542
|
-
@staticmethod
|
543
|
-
def ByWire(wire: topologic.Wire, tolerance: float = 0.0001, silent=False) -> topologic.Face:
|
544
|
-
"""
|
545
|
-
Creates a face from the input closed wire.
|
546
|
-
|
547
|
-
Parameters
|
548
|
-
----------
|
549
|
-
wire : topologic.Wire
|
550
|
-
The input wire.
|
551
|
-
tolerance : float , optional
|
552
|
-
The desired tolerance. The default is 0.0001.
|
553
|
-
silent : bool , optional
|
554
|
-
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
|
555
|
-
|
556
|
-
Returns
|
557
|
-
-------
|
558
|
-
topologic.Face or list
|
559
|
-
The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.
|
560
|
-
|
561
|
-
"""
|
562
|
-
from topologicpy.Vertex import Vertex
|
563
|
-
from topologicpy.Wire import Wire
|
564
|
-
from topologicpy.Shell import Shell
|
565
|
-
from topologicpy.Cluster import Cluster
|
566
|
-
from topologicpy.Topology import Topology
|
567
|
-
from topologicpy.Dictionary import Dictionary
|
568
|
-
import random
|
569
|
-
|
570
|
-
def triangulateWire(wire):
|
571
|
-
wire = Topology.RemoveCollinearEdges(wire)
|
572
|
-
vertices = Topology.Vertices(wire)
|
573
|
-
shell = Shell.Delaunay(vertices)
|
574
|
-
if isinstance(shell, topologic.Topology):
|
575
|
-
return Topology.Faces(shell)
|
576
|
-
else:
|
577
|
-
return []
|
578
|
-
if not isinstance(wire, topologic.Wire):
|
579
|
-
if not silent:
|
580
|
-
print("Face.ByWire - Error: The input wire parameter is not a valid topologic wire. Returning None.")
|
581
|
-
return None
|
582
|
-
if not Wire.IsClosed(wire):
|
583
|
-
if not silent:
|
584
|
-
print("Face.ByWire - Error: The input wire parameter is not a closed topologic wire. Returning None.")
|
585
|
-
return None
|
586
|
-
|
587
|
-
edges = Wire.Edges(wire)
|
588
|
-
wire = Topology.SelfMerge(Cluster.ByTopologies(edges), tolerance=tolerance)
|
589
|
-
vertices = Topology.Vertices(wire)
|
590
|
-
fList = []
|
591
|
-
if isinstance(wire, topologic.Wire):
|
592
|
-
try:
|
593
|
-
fList = topologic.Face.ByExternalBoundary(wire)
|
594
|
-
except:
|
595
|
-
if not silent:
|
596
|
-
print("Face.ByWire - Warning: Could not create face by external boundary. Trying other methods.")
|
597
|
-
if len(vertices) > 3:
|
598
|
-
fList = triangulateWire(wire)
|
599
|
-
else:
|
600
|
-
fList = []
|
601
|
-
|
602
|
-
if not isinstance(fList, list):
|
603
|
-
fList = [fList]
|
604
|
-
|
605
|
-
returnList = []
|
606
|
-
for f in fList:
|
607
|
-
if Face.Area(f) < 0:
|
608
|
-
wire = Face.ExternalBoundary(f)
|
609
|
-
wire = Wire.Invert(wire)
|
610
|
-
try:
|
611
|
-
f = topologic.Face.ByExternalBoundary(wire)
|
612
|
-
returnList.append(f)
|
613
|
-
except:
|
614
|
-
pass
|
615
|
-
else:
|
616
|
-
returnList.append(f)
|
617
|
-
if len(returnList) == 0:
|
618
|
-
if not silent:
|
619
|
-
print("Face.ByWire - Error: Could not build a face from the input wire parameter. Returning None.")
|
620
|
-
return None
|
621
|
-
elif len(returnList) == 1:
|
622
|
-
return returnList[0]
|
623
|
-
else:
|
624
|
-
if not silent:
|
625
|
-
print("Face.ByWire - Warning: Could not build a single face from the input wire parameter. Returning a list of faces.")
|
626
|
-
return returnList
|
627
|
-
|
628
|
-
@staticmethod
|
629
|
-
def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = [], tolerance: float = 0.0001, silent: bool = False) -> topologic.Face:
|
630
|
-
"""
|
631
|
-
Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).
|
632
|
-
|
633
|
-
Parameters
|
634
|
-
----------
|
635
|
-
externalBoundary : topologic.Wire
|
636
|
-
The input external boundary.
|
637
|
-
internalBoundaries : list , optional
|
638
|
-
The input list of internal boundaries (closed wires). The default is an empty list.
|
639
|
-
tolerance : float , optional
|
640
|
-
The desired tolerance. The default is 0.0001.
|
641
|
-
silent : bool , optional
|
642
|
-
If set to False, error messages are printed. Otherwise, they are not. The default is False.
|
643
|
-
|
644
|
-
Returns
|
645
|
-
-------
|
646
|
-
topologic.Face
|
647
|
-
The created face.
|
648
|
-
|
649
|
-
"""
|
650
|
-
if not isinstance(externalBoundary, topologic.Wire):
|
651
|
-
if not silent:
|
652
|
-
print("Face.ByWires - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.")
|
653
|
-
return None
|
654
|
-
if not Wire.IsClosed(externalBoundary):
|
655
|
-
if not silent:
|
656
|
-
print("Face.ByWires - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.")
|
657
|
-
return None
|
658
|
-
ibList = [x for x in internalBoundaries if isinstance(x, topologic.Wire) and Wire.IsClosed(x)]
|
659
|
-
face = None
|
660
|
-
try:
|
661
|
-
face = topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList, tolerance)
|
662
|
-
except:
|
663
|
-
if not silent:
|
664
|
-
print("Face.ByWires - Error: The operation failed. Returning None.")
|
665
|
-
face = None
|
666
|
-
return face
|
667
|
-
|
668
|
-
|
669
|
-
@staticmethod
|
670
|
-
def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None, tolerance: float = 0.0001, silent: bool = False) -> topologic.Face:
|
671
|
-
"""
|
672
|
-
Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).
|
673
|
-
|
674
|
-
Parameters
|
675
|
-
----------
|
676
|
-
externalBoundary : topologic.Wire
|
677
|
-
The input external boundary (closed wire).
|
678
|
-
internalBoundariesCluster : topologic.Cluster
|
679
|
-
The input cluster of internal boundaries (closed wires). The default is None.
|
680
|
-
tolerance : float , optional
|
681
|
-
The desired tolerance. The default is 0.0001.
|
682
|
-
silent : bool , optional
|
683
|
-
If set to False, error messages are printed. Otherwise, they are not. The default is False.
|
684
|
-
|
685
|
-
Returns
|
686
|
-
-------
|
687
|
-
topologic.Face
|
688
|
-
The created face.
|
689
|
-
|
690
|
-
"""
|
691
|
-
from topologicpy.Wire import Wire
|
692
|
-
from topologicpy.Cluster import Cluster
|
693
|
-
if not isinstance(externalBoundary, topologic.Wire):
|
694
|
-
if not silent:
|
695
|
-
print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.")
|
696
|
-
return None
|
697
|
-
if not Wire.IsClosed(externalBoundary):
|
698
|
-
if not silent:
|
699
|
-
print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.")
|
700
|
-
return None
|
701
|
-
if not internalBoundariesCluster:
|
702
|
-
internalBoundaries = []
|
703
|
-
elif not isinstance(internalBoundariesCluster, topologic.Cluster):
|
704
|
-
if not silent:
|
705
|
-
print("Face.ByWiresCluster - Error: The input internalBoundariesCluster parameter is not a valid topologic cluster. Returning None.")
|
706
|
-
return None
|
707
|
-
else:
|
708
|
-
internalBoundaries = Cluster.Wires(internalBoundariesCluster)
|
709
|
-
return Face.ByWires(externalBoundary, internalBoundaries, tolerance=tolerance, silent=silent)
|
710
|
-
|
711
|
-
@staticmethod
|
712
|
-
def NorthArrow(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0,
|
713
|
-
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
714
|
-
"""
|
715
|
-
Creates a north arrow.
|
716
|
-
|
717
|
-
Parameters
|
718
|
-
----------
|
719
|
-
origin : topologic.Vertex, optional
|
720
|
-
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
721
|
-
radius : float , optional
|
722
|
-
The radius of the circle. The default is 1.
|
723
|
-
sides : int , optional
|
724
|
-
The number of sides of the circle. The default is 16.
|
725
|
-
direction : list , optional
|
726
|
-
The vector representing the up direction of the circle. The default is [0, 0, 1].
|
727
|
-
northAngle : float , optional
|
728
|
-
The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
|
729
|
-
placement : str , optional
|
730
|
-
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".
|
731
|
-
tolerance : float , optional
|
732
|
-
The desired tolerance. The default is 0.0001.
|
733
|
-
|
734
|
-
Returns
|
735
|
-
-------
|
736
|
-
topologic.Face
|
737
|
-
The created circle.
|
738
|
-
|
739
|
-
"""
|
740
|
-
from topologicpy.Topology import Topology
|
741
|
-
from topologicpy.Vertex import Vertex
|
742
|
-
if not origin:
|
743
|
-
origin = Vertex.Origin()
|
744
|
-
|
745
|
-
c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0, 0, 1], placement="center", tolerance=tolerance)
|
746
|
-
r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft")
|
747
|
-
r = Topology.Translate(r, -0.005*radius,0,0)
|
748
|
-
arrow = Topology.Difference(c, r, tolerance=tolerance)
|
749
|
-
arrow = Topology.Rotate(arrow, origin=Vertex.Origin(), axis=[0, 0, 1], angle=northAngle)
|
750
|
-
if placement.lower() == "lowerleft":
|
751
|
-
arrow = Topology.Translate(arrow, radius, radius, 0)
|
752
|
-
elif placement.lower() == "upperleft":
|
753
|
-
arrow = Topology.Translate(arrow, radius, -radius, 0)
|
754
|
-
elif placement.lower() == "lowerright":
|
755
|
-
arrow = Topology.Translate(arrow, -radius, radius, 0)
|
756
|
-
elif placement.lower() == "upperright":
|
757
|
-
arrow = Topology.Translate(arrow, -radius, -radius, 0)
|
758
|
-
arrow = Topology.Place(arrow, originA=Vertex.Origin(), originB=origin)
|
759
|
-
arrow = Topology.Orient(arrow, orign=origin, direction=direction)
|
760
|
-
return arrow
|
761
|
-
|
762
|
-
@staticmethod
|
763
|
-
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],
|
764
|
-
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
765
|
-
"""
|
766
|
-
Creates a circle.
|
767
|
-
|
768
|
-
Parameters
|
769
|
-
----------
|
770
|
-
origin : topologic.Vertex, optional
|
771
|
-
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
772
|
-
radius : float , optional
|
773
|
-
The radius of the circle. The default is 1.
|
774
|
-
sides : int , optional
|
775
|
-
The number of sides of the circle. The default is 16.
|
776
|
-
fromAngle : float , optional
|
777
|
-
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
778
|
-
toAngle : float , optional
|
779
|
-
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
780
|
-
direction : list , optional
|
781
|
-
The vector representing the up direction of the circle. The default is [0, 0, 1].
|
782
|
-
placement : str , optional
|
783
|
-
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".
|
784
|
-
tolerance : float , optional
|
785
|
-
The desired tolerance. The default is 0.0001.
|
786
|
-
|
787
|
-
Returns
|
788
|
-
-------
|
789
|
-
topologic.Face
|
790
|
-
The created circle.
|
791
|
-
|
792
|
-
"""
|
793
|
-
from topologicpy.Wire import Wire
|
794
|
-
wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
|
795
|
-
if not isinstance(wire, topologic.Wire):
|
796
|
-
return None
|
797
|
-
return Face.ByWire(wire, tolerance=tolerance)
|
798
|
-
|
799
|
-
@staticmethod
|
800
|
-
def Compactness(face: topologic.Face, mantissa: int = 6) -> float:
|
801
|
-
"""
|
802
|
-
Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
803
|
-
|
804
|
-
Parameters
|
805
|
-
----------
|
806
|
-
face : topologic.Face
|
807
|
-
The input face.
|
808
|
-
mantissa : int , optional
|
809
|
-
The desired length of the mantissa. The default is 6.
|
810
|
-
|
811
|
-
Returns
|
812
|
-
-------
|
813
|
-
float
|
814
|
-
The compactness measure of the input face.
|
815
|
-
|
816
|
-
"""
|
817
|
-
exb = face.ExternalBoundary()
|
818
|
-
edges = []
|
819
|
-
_ = exb.Edges(None, edges)
|
820
|
-
perimeter = 0.0
|
821
|
-
for anEdge in edges:
|
822
|
-
perimeter = perimeter + abs(topologic.EdgeUtility.Length(anEdge))
|
823
|
-
area = abs(Face.Area(face))
|
824
|
-
compactness = 0
|
825
|
-
#From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
826
|
-
|
827
|
-
if area <= 0:
|
828
|
-
return None
|
829
|
-
if perimeter <= 0:
|
830
|
-
return None
|
831
|
-
compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
|
832
|
-
return round(compactness, mantissa)
|
833
|
-
|
834
|
-
@staticmethod
|
835
|
-
def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 6) -> float:
|
836
|
-
"""
|
837
|
-
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.
|
838
|
-
|
839
|
-
Parameters
|
840
|
-
----------
|
841
|
-
face : topologic.Face
|
842
|
-
The input face.
|
843
|
-
north : list , optional
|
844
|
-
The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
|
845
|
-
mantissa : int, optional
|
846
|
-
The length of the desired mantissa. The default is
|
847
|
-
tolerance : float , optional
|
848
|
-
The desired tolerance. The default is 0.0001.
|
849
|
-
|
850
|
-
Returns
|
851
|
-
-------
|
852
|
-
float
|
853
|
-
The horizontal compass angle in degrees between the direction of the face and the second input vector.
|
854
|
-
|
855
|
-
"""
|
856
|
-
from topologicpy.Vector import Vector
|
857
|
-
if not isinstance(face, topologic.Face):
|
858
|
-
return None
|
859
|
-
if not north:
|
860
|
-
north = Vector.North()
|
861
|
-
dirA = Face.NormalAtParameters(face,mantissa=mantissa)
|
862
|
-
return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)
|
863
|
-
|
864
|
-
@staticmethod
|
865
|
-
def Edges(face: topologic.Face) -> list:
|
866
|
-
"""
|
867
|
-
Returns the edges of the input face.
|
868
|
-
|
869
|
-
Parameters
|
870
|
-
----------
|
871
|
-
face : topologic.Face
|
872
|
-
The input face.
|
873
|
-
|
874
|
-
Returns
|
875
|
-
-------
|
876
|
-
list
|
877
|
-
The list of edges.
|
878
|
-
|
879
|
-
"""
|
880
|
-
if not isinstance(face, topologic.Face):
|
881
|
-
return None
|
882
|
-
edges = []
|
883
|
-
_ = face.Edges(None, edges)
|
884
|
-
return edges
|
885
|
-
|
886
|
-
@staticmethod
|
887
|
-
def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0, 0, 1],
|
888
|
-
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
889
|
-
"""
|
890
|
-
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
|
891
|
-
|
892
|
-
Parameters
|
893
|
-
----------
|
894
|
-
origin : topologic.Vertex , optional
|
895
|
-
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).
|
896
|
-
radius : float , optional
|
897
|
-
The radius of the hexagon determining the size of the tile. The default is 0.5.
|
898
|
-
direction : list , optional
|
899
|
-
The vector representing the up direction of the ellipse. The default is [0, 0, 1].
|
900
|
-
placement : str , optional
|
901
|
-
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".
|
902
|
-
tolerance : float , optional
|
903
|
-
The desired tolerance. The default is 0.0001.
|
904
|
-
|
905
|
-
Returns
|
906
|
-
--------
|
907
|
-
topologic.Face
|
908
|
-
The created Einstein tile.
|
909
|
-
|
910
|
-
"""
|
911
|
-
from topologicpy.Wire import Wire
|
912
|
-
|
913
|
-
wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
|
914
|
-
if not isinstance(wire, topologic.Wire):
|
915
|
-
print("Face.Einstein - Error: Could not create base wire for the Einstein tile. Returning None.")
|
916
|
-
return None
|
917
|
-
return Face.ByWire(wire, tolerance=tolerance)
|
918
|
-
|
919
|
-
@staticmethod
|
920
|
-
def ExteriorAngles(face: topologic.Face, includeInternalBoundaries=False, mantissa: int = 6) -> list:
|
921
|
-
"""
|
922
|
-
Returns the exterior angles of the input face in degrees. The face must be planar.
|
923
|
-
|
924
|
-
Parameters
|
925
|
-
----------
|
926
|
-
face : topologic.Face
|
927
|
-
The input face.
|
928
|
-
includeInternalBoundaries : bool , optional
|
929
|
-
If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list
|
930
|
-
of exterior angles of the external boundary and the second list will contain lists of the exterior angles of each of the
|
931
|
-
internal boundaries (holes). For example: [[270,270,270,270], [[270,270,270,270],[300,300,300]]]. If not, the returned list will be
|
932
|
-
a simple list of interior angles of the external boundary. For example: [270,270,270,270]. Please note that that the interior angles of the
|
933
|
-
internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
|
934
|
-
mantissa : int , optional
|
935
|
-
The desired length of the mantissa. The default is 6.
|
936
|
-
Returns
|
937
|
-
-------
|
938
|
-
list
|
939
|
-
The list of exterior angles.
|
940
|
-
"""
|
941
|
-
|
942
|
-
from topologicpy.Wire import Wire
|
943
|
-
|
944
|
-
if not isinstance(face, topologic.Face):
|
945
|
-
print("Face.ExteriorAngles - Error: The input face parameter is not a valid face. Returning None.")
|
946
|
-
return None
|
947
|
-
eb = Face.ExternalBoundary(face)
|
948
|
-
return_list = Wire.ExteriorAngles(eb, mantissa=mantissa)
|
949
|
-
if includeInternalBoundaries:
|
950
|
-
internal_boundaries = Face.InternalBoundaries(face)
|
951
|
-
ib_i_a_list = []
|
952
|
-
if len(internal_boundaries) > 0:
|
953
|
-
for ib in internal_boundaries:
|
954
|
-
ib_interior_angles = Wire.InteriorAngles(ib, mantissa=mantissa)
|
955
|
-
ib_i_a_list.append(ib_interior_angles)
|
956
|
-
if len(ib_i_a_list) > 0:
|
957
|
-
return_list = [return_list]+[ib_i_a_list]
|
958
|
-
return return_list
|
959
|
-
|
960
|
-
@staticmethod
|
961
|
-
def ExternalBoundary(face: topologic.Face) -> topologic.Wire:
|
962
|
-
"""
|
963
|
-
Returns the external boundary (closed wire) of the input face.
|
964
|
-
|
965
|
-
Parameters
|
966
|
-
----------
|
967
|
-
face : topologic.Face
|
968
|
-
The input face.
|
969
|
-
|
970
|
-
Returns
|
971
|
-
-------
|
972
|
-
topologic.Wire
|
973
|
-
The external boundary of the input face.
|
974
|
-
|
975
|
-
"""
|
976
|
-
return face.ExternalBoundary()
|
977
|
-
|
978
|
-
@staticmethod
|
979
|
-
def FacingToward(face: topologic.Face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
|
980
|
-
"""
|
981
|
-
Returns True if the input face is facing toward the input direction.
|
982
|
-
|
983
|
-
Parameters
|
984
|
-
----------
|
985
|
-
face : topologic.Face
|
986
|
-
The input face.
|
987
|
-
direction : list , optional
|
988
|
-
The input direction. The default is [0,0,-1].
|
989
|
-
asVertex : bool , optional
|
990
|
-
If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
|
991
|
-
tolerance : float , optional
|
992
|
-
The desired tolerance. The default is 0.0001.
|
993
|
-
|
994
|
-
Returns
|
995
|
-
-------
|
996
|
-
bool
|
997
|
-
True if the face is facing toward the direction. False otherwise.
|
998
|
-
|
999
|
-
"""
|
1000
|
-
faceNormal = topologic.FaceUtility.NormalAtParameters(face,0.5, 0.5)
|
1001
|
-
faceCenter = topologic.FaceUtility.VertexAtParameters(face,0.5,0.5)
|
1002
|
-
cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
|
1003
|
-
try:
|
1004
|
-
vList = [direction.X(), direction.Y(), direction.Z()]
|
1005
|
-
except:
|
1006
|
-
try:
|
1007
|
-
vList = [direction[0], direction[1], direction[2]]
|
1008
|
-
except:
|
1009
|
-
raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
|
1010
|
-
if asVertex:
|
1011
|
-
dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
|
1012
|
-
else:
|
1013
|
-
dV = vList
|
1014
|
-
uV = Vector.Normalize(dV)
|
1015
|
-
dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
|
1016
|
-
if dot < tolerance:
|
1017
|
-
return False
|
1018
|
-
return True
|
1019
|
-
|
1020
|
-
@staticmethod
|
1021
|
-
def Harmonize(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Face:
|
1022
|
-
"""
|
1023
|
-
Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.
|
1024
|
-
|
1025
|
-
Parameters
|
1026
|
-
----------
|
1027
|
-
face : topologic.Face
|
1028
|
-
The input face.
|
1029
|
-
tolerance : float , optional
|
1030
|
-
The desired tolerance. The default is 0.0001.
|
1031
|
-
|
1032
|
-
Returns
|
1033
|
-
-------
|
1034
|
-
topologic.Face
|
1035
|
-
The harmonized face.
|
1036
|
-
|
1037
|
-
"""
|
1038
|
-
from topologicpy.Vertex import Vertex
|
1039
|
-
from topologicpy.Wire import Wire
|
1040
|
-
from topologicpy.Topology import Topology
|
1041
|
-
from topologicpy.Dictionary import Dictionary
|
1042
|
-
|
1043
|
-
if not isinstance(face, topologic.Face):
|
1044
|
-
print("Face.Harmonize - Error: The input face parameter is not a valid face. Returning None.")
|
1045
|
-
return None
|
1046
|
-
normal = Face.Normal(face)
|
1047
|
-
origin = Topology.Centroid(face)
|
1048
|
-
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1049
|
-
world_origin = Vertex.Origin()
|
1050
|
-
vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
|
1051
|
-
harmonizedEB = Wire.ByVertices(vertices)
|
1052
|
-
internalBoundaries = Face.InternalBoundaries(flatFace)
|
1053
|
-
harmonizedIB = []
|
1054
|
-
for ib in internalBoundaries:
|
1055
|
-
ibVertices = Wire.Vertices(ib)
|
1056
|
-
harmonizedIB.append(Wire.ByVertices(ibVertices))
|
1057
|
-
harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB, tolerance=tolerance)
|
1058
|
-
harmonizedFace = Topology.Unflatten(harmonizedFace, origin=origin, direction=normal)
|
1059
|
-
return harmonizedFace
|
1060
|
-
|
1061
|
-
@staticmethod
|
1062
|
-
def InteriorAngles(face: topologic.Face, includeInternalBoundaries: bool = False, mantissa: int = 6) -> list:
|
1063
|
-
"""
|
1064
|
-
Returns the interior angles of the input face in degrees. The face must be planar.
|
1065
|
-
|
1066
|
-
Parameters
|
1067
|
-
----------
|
1068
|
-
face : topologic.Face
|
1069
|
-
The input face.
|
1070
|
-
includeInternalBoundaries : bool , optional
|
1071
|
-
If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list
|
1072
|
-
of interior angles of the external boundary and the second list will contain lists of the interior angles of each of the
|
1073
|
-
internal boundaries (holes). For example: [[90,90,90,90], [[90,90,90,90],[60,60,60]]]. If not, the returned list will be
|
1074
|
-
a simple list of interior angles of the external boundary. For example: [90,90,90,90]. Please note that that the interior angles of the
|
1075
|
-
internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
|
1076
|
-
mantissa : int , optional
|
1077
|
-
The desired length of the mantissa. The default is 6.
|
1078
|
-
Returns
|
1079
|
-
-------
|
1080
|
-
list
|
1081
|
-
The list of interior angles.
|
1082
|
-
"""
|
1083
|
-
|
1084
|
-
from topologicpy.Wire import Wire
|
1085
|
-
|
1086
|
-
if not isinstance(face, topologic.Face):
|
1087
|
-
print("Face.InteriorAngles - Error: The input face parameter is not a valid face. Returning None.")
|
1088
|
-
return None
|
1089
|
-
eb = Face.ExternalBoundary(face)
|
1090
|
-
return_list = Wire.InteriorAngles(eb, mantissa=mantissa)
|
1091
|
-
if includeInternalBoundaries:
|
1092
|
-
internal_boundaries = Face.InternalBoundaries(face)
|
1093
|
-
ib_i_a_list = []
|
1094
|
-
if len(internal_boundaries) > 0:
|
1095
|
-
for ib in internal_boundaries:
|
1096
|
-
ib_interior_angles = Wire.ExteriorAngles(ib, mantissa=mantissa)
|
1097
|
-
ib_i_a_list.append(ib_interior_angles)
|
1098
|
-
if len(ib_i_a_list) > 0:
|
1099
|
-
return_list = [return_list]+[ib_i_a_list]
|
1100
|
-
return return_list
|
1101
|
-
|
1102
|
-
@staticmethod
|
1103
|
-
def InternalBoundaries(face: topologic.Face) -> list:
|
1104
|
-
"""
|
1105
|
-
Returns the internal boundaries (closed wires) of the input face.
|
1106
|
-
|
1107
|
-
Parameters
|
1108
|
-
----------
|
1109
|
-
face : topologic.Face
|
1110
|
-
The input face.
|
1111
|
-
|
1112
|
-
Returns
|
1113
|
-
-------
|
1114
|
-
list
|
1115
|
-
The list of internal boundaries (closed wires).
|
1116
|
-
|
1117
|
-
"""
|
1118
|
-
if not isinstance(face, topologic.Face):
|
1119
|
-
return None
|
1120
|
-
wires = []
|
1121
|
-
_ = face.InternalBoundaries(wires)
|
1122
|
-
return list(wires)
|
1123
|
-
|
1124
|
-
@staticmethod
|
1125
|
-
def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Vertex:
|
1126
|
-
"""
|
1127
|
-
Creates a vertex guaranteed to be inside the input face.
|
1128
|
-
|
1129
|
-
Parameters
|
1130
|
-
----------
|
1131
|
-
face : topologic.Face
|
1132
|
-
The input face.
|
1133
|
-
tolerance : float , optional
|
1134
|
-
The desired tolerance. The default is 0.0001.
|
1135
|
-
|
1136
|
-
Returns
|
1137
|
-
-------
|
1138
|
-
topologic.Vertex
|
1139
|
-
The created vertex.
|
1140
|
-
|
1141
|
-
"""
|
1142
|
-
from topologicpy.Vertex import Vertex
|
1143
|
-
from topologicpy.Topology import Topology
|
1144
|
-
if not isinstance(face, topologic.Face):
|
1145
|
-
return None
|
1146
|
-
v = Topology.Centroid(face)
|
1147
|
-
if Vertex.IsInternal(v, face, tolerance=tolerance):
|
1148
|
-
return v
|
1149
|
-
l = [0.4,0.6,0.3,0.7,0.2,0.8,0.1,0.9]
|
1150
|
-
for u in l:
|
1151
|
-
for v in l:
|
1152
|
-
v = Face.VertexByParameters(face, u, v)
|
1153
|
-
if Vertex.IsInternal(v, face, tolerance=tolerance):
|
1154
|
-
return v
|
1155
|
-
v = topologic.FaceUtility.InternalVertex(face, tolerance)
|
1156
|
-
return v
|
1157
|
-
|
1158
|
-
@staticmethod
|
1159
|
-
def Invert(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Face:
|
1160
|
-
"""
|
1161
|
-
Creates a face that is an inverse (mirror) of the input face.
|
1162
|
-
|
1163
|
-
Parameters
|
1164
|
-
----------
|
1165
|
-
face : topologic.Face
|
1166
|
-
The input face.
|
1167
|
-
tolerance : float , optional
|
1168
|
-
The desired tolerance. The default is 0.0001.
|
1169
|
-
|
1170
|
-
Returns
|
1171
|
-
-------
|
1172
|
-
topologic.Face
|
1173
|
-
The inverted face.
|
1174
|
-
|
1175
|
-
"""
|
1176
|
-
from topologicpy.Wire import Wire
|
1177
|
-
|
1178
|
-
if not isinstance(face, topologic.Face):
|
1179
|
-
return None
|
1180
|
-
eb = Face.ExternalBoundary(face)
|
1181
|
-
vertices = Wire.Vertices(eb)
|
1182
|
-
vertices.reverse()
|
1183
|
-
inverted_wire = Wire.ByVertices(vertices)
|
1184
|
-
internal_boundaries = Face.InternalBoundaries(face)
|
1185
|
-
if not internal_boundaries:
|
1186
|
-
inverted_face = Face.ByWire(inverted_wire, tolerance=tolerance)
|
1187
|
-
else:
|
1188
|
-
inverted_face = Face.ByWires(inverted_wire, internal_boundaries, tolerance=tolerance)
|
1189
|
-
return inverted_face
|
1190
|
-
|
1191
|
-
@staticmethod
|
1192
|
-
def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) -> bool:
|
1193
|
-
"""
|
1194
|
-
Returns True if the two input faces are coplanar. Returns False otherwise.
|
1195
|
-
|
1196
|
-
Parameters
|
1197
|
-
----------
|
1198
|
-
faceA : topologic.Face
|
1199
|
-
The first input face.
|
1200
|
-
faceB : topologic.Face
|
1201
|
-
The second input face
|
1202
|
-
tolerance : float , optional
|
1203
|
-
The desired tolerance. The deafault is 0.0001.
|
1204
|
-
|
1205
|
-
Raises
|
1206
|
-
------
|
1207
|
-
Exception
|
1208
|
-
Raises an exception if the angle between the two input faces cannot be determined.
|
1209
|
-
|
1210
|
-
Returns
|
1211
|
-
-------
|
1212
|
-
bool
|
1213
|
-
True if the two input faces are coplanar. False otherwise.
|
1214
|
-
|
1215
|
-
"""
|
1216
|
-
if not isinstance(faceA, topologic.Face):
|
1217
|
-
print("Face.IsInide - Error: The input faceA parameter is not a valid topologic face. Returning None.")
|
1218
|
-
return None
|
1219
|
-
if not isinstance(faceB, topologic.Face):
|
1220
|
-
print("Face.IsInide - Error: The input faceB parameter is not a valid topologic face. Returning None.")
|
1221
|
-
return None
|
1222
|
-
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
|
1223
|
-
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
|
1224
|
-
return Vector.IsCollinear(dirA, dirB)
|
1225
|
-
|
1226
|
-
@staticmethod
|
1227
|
-
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:
|
1228
|
-
"""
|
1229
|
-
Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.
|
1230
|
-
|
1231
|
-
Parameters
|
1232
|
-
----------
|
1233
|
-
face : topologic.Face
|
1234
|
-
The input face.
|
1235
|
-
resolution : int , optional
|
1236
|
-
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.
|
1237
|
-
externalVertices : bool , optional
|
1238
|
-
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.
|
1239
|
-
internalVertices : bool , optional
|
1240
|
-
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.
|
1241
|
-
toLeavesOnly : bool , optional
|
1242
|
-
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.
|
1243
|
-
angTolerance : float , optional
|
1244
|
-
The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
|
1245
|
-
tolerance : float , optional
|
1246
|
-
The desired tolerance. The default is 0.0001.
|
1247
|
-
|
1248
|
-
Returns
|
1249
|
-
-------
|
1250
|
-
topologic.Wire
|
1251
|
-
The medial axis of the input face.
|
1252
|
-
|
1253
|
-
"""
|
1254
|
-
from topologicpy.Vertex import Vertex
|
1255
|
-
from topologicpy.Edge import Edge
|
1256
|
-
from topologicpy.Wire import Wire
|
1257
|
-
from topologicpy.Shell import Shell
|
1258
|
-
from topologicpy.Cluster import Cluster
|
1259
|
-
from topologicpy.Topology import Topology
|
1260
|
-
from topologicpy.Dictionary import Dictionary
|
1261
|
-
|
1262
|
-
def touchesEdge(vertex,edges, tolerance=0.0001):
|
1263
|
-
if not isinstance(vertex, topologic.Vertex):
|
1264
|
-
return False
|
1265
|
-
for edge in edges:
|
1266
|
-
u = Edge.ParameterAtVertex(edge, vertex, mantissa=6)
|
1267
|
-
if not u:
|
1268
|
-
continue
|
1269
|
-
if 0<u<1:
|
1270
|
-
return True
|
1271
|
-
return False
|
1272
|
-
|
1273
|
-
# Flatten the input face
|
1274
|
-
origin = Topology.Centroid(face)
|
1275
|
-
normal = Face.Normal(face)
|
1276
|
-
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1277
|
-
|
1278
|
-
# Create a Vertex at the world's origin (0, 0, 0)
|
1279
|
-
world_origin = Vertex.Origin()
|
1280
|
-
|
1281
|
-
faceEdges = Face.Edges(flatFace)
|
1282
|
-
vertices = []
|
1283
|
-
resolution = 10 - resolution
|
1284
|
-
resolution = min(max(resolution, 1), 10)
|
1285
|
-
for e in faceEdges:
|
1286
|
-
for n in range(resolution, 100, resolution):
|
1287
|
-
vertices.append(Edge.VertexByParameter(e,n*0.01))
|
1288
|
-
|
1289
|
-
voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
|
1290
|
-
voronoiEdges = Shell.Edges(voronoi)
|
1291
|
-
|
1292
|
-
medialAxisEdges = []
|
1293
|
-
for e in voronoiEdges:
|
1294
|
-
sv = Edge.StartVertex(e)
|
1295
|
-
ev = Edge.EndVertex(e)
|
1296
|
-
svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
|
1297
|
-
evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
|
1298
|
-
if not svTouchesEdge and not evTouchesEdge:
|
1299
|
-
medialAxisEdges.append(e)
|
1300
|
-
|
1301
|
-
extBoundary = Face.ExternalBoundary(flatFace)
|
1302
|
-
extVertices = Wire.Vertices(extBoundary)
|
1303
|
-
|
1304
|
-
intBoundaries = Face.InternalBoundaries(flatFace)
|
1305
|
-
intVertices = []
|
1306
|
-
for ib in intBoundaries:
|
1307
|
-
intVertices = intVertices+Wire.Vertices(ib)
|
1308
|
-
|
1309
|
-
theVertices = []
|
1310
|
-
if internalVertices:
|
1311
|
-
theVertices = theVertices+intVertices
|
1312
|
-
if externalVertices:
|
1313
|
-
theVertices = theVertices+extVertices
|
1314
|
-
|
1315
|
-
tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance)
|
1316
|
-
if isinstance(tempWire, topologic.Wire) and angTolerance > 0:
|
1317
|
-
tempWire = Wire.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
|
1318
|
-
medialAxisEdges = Wire.Edges(tempWire)
|
1319
|
-
for v in theVertices:
|
1320
|
-
nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)
|
1321
|
-
|
1322
|
-
if isinstance(nv, topologic.Vertex):
|
1323
|
-
if toLeavesOnly:
|
1324
|
-
adjVertices = Topology.AdjacentTopologies(nv, tempWire)
|
1325
|
-
if len(adjVertices) < 2:
|
1326
|
-
medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance))
|
1327
|
-
else:
|
1328
|
-
medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance))
|
1329
|
-
medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance)
|
1330
|
-
if isinstance(medialAxis, topologic.Wire) and angTolerance > 0:
|
1331
|
-
medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
|
1332
|
-
medialAxis = Topology.Unflatten(medialAxis, origin=origin,direction=normal)
|
1333
|
-
return medialAxis
|
1334
|
-
|
1335
|
-
@staticmethod
|
1336
|
-
def Normal(face: topologic.Face, outputType: str = "xyz", mantissa: int = 6) -> list:
|
1337
|
-
"""
|
1338
|
-
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
|
1339
|
-
|
1340
|
-
Parameters
|
1341
|
-
----------
|
1342
|
-
face : topologic.Face
|
1343
|
-
The input face.
|
1344
|
-
outputType : string , optional
|
1345
|
-
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
|
1346
|
-
mantissa : int , optional
|
1347
|
-
The desired length of the mantissa. The default is 6.
|
1348
|
-
|
1349
|
-
Returns
|
1350
|
-
-------
|
1351
|
-
list
|
1352
|
-
The normal vector to the input face. This is computed at the approximate center of the face.
|
1353
|
-
|
1354
|
-
"""
|
1355
|
-
return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
|
1356
|
-
|
1357
|
-
@staticmethod
|
1358
|
-
def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 6) -> list:
|
1359
|
-
"""
|
1360
|
-
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
|
1361
|
-
|
1362
|
-
Parameters
|
1363
|
-
----------
|
1364
|
-
face : topologic.Face
|
1365
|
-
The input face.
|
1366
|
-
u : float , optional
|
1367
|
-
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
|
1368
|
-
v : float , optional
|
1369
|
-
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
|
1370
|
-
outputType : string , optional
|
1371
|
-
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
|
1372
|
-
mantissa : int , optional
|
1373
|
-
The desired length of the mantissa. The default is 6.
|
1374
|
-
|
1375
|
-
Returns
|
1376
|
-
-------
|
1377
|
-
list
|
1378
|
-
The normal vector to the input face.
|
1379
|
-
|
1380
|
-
"""
|
1381
|
-
returnResult = []
|
1382
|
-
try:
|
1383
|
-
coords = topologic.FaceUtility.NormalAtParameters(face, u, v)
|
1384
|
-
x = round(coords[0], mantissa)
|
1385
|
-
y = round(coords[1], mantissa)
|
1386
|
-
z = round(coords[2], mantissa)
|
1387
|
-
outputType = list(outputType.lower())
|
1388
|
-
for axis in outputType:
|
1389
|
-
if axis == "x":
|
1390
|
-
returnResult.append(x)
|
1391
|
-
elif axis == "y":
|
1392
|
-
returnResult.append(y)
|
1393
|
-
elif axis == "z":
|
1394
|
-
returnResult.append(z)
|
1395
|
-
except:
|
1396
|
-
returnResult = None
|
1397
|
-
return returnResult
|
1398
|
-
|
1399
|
-
@staticmethod
|
1400
|
-
def NormalEdge(face: topologic.Face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False) -> topologic.Edge:
|
1401
|
-
"""
|
1402
|
-
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.
|
1403
|
-
|
1404
|
-
Parameters
|
1405
|
-
----------
|
1406
|
-
face : topologic.Face
|
1407
|
-
The input face.
|
1408
|
-
length : float , optional
|
1409
|
-
The desired length of the normal edge. The default is 1.
|
1410
|
-
tolerance : float , optional
|
1411
|
-
The desired tolerance. The default is 0.0001.
|
1412
|
-
|
1413
|
-
Returns
|
1414
|
-
-------
|
1415
|
-
topologic.Edge
|
1416
|
-
The created normal edge to the input face. This is computed at the approximate center of the face.
|
1417
|
-
|
1418
|
-
"""
|
1419
|
-
from topologicpy.Edge import Edge
|
1420
|
-
|
1421
|
-
if not isinstance(face, topologic.Face):
|
1422
|
-
if not silent:
|
1423
|
-
print("Face.NormalEdge - Error: The input face parameter is not a valid face. Retuning None.")
|
1424
|
-
return None
|
1425
|
-
if length < tolerance:
|
1426
|
-
if not silent:
|
1427
|
-
print("Face.NormalEdge - Error: The input length parameter is less than the input tolerance. Retuning None.")
|
1428
|
-
return None
|
1429
|
-
iv = Face.InternalVertex(face)
|
1430
|
-
u, v = Face.VertexParameters(face, iv)
|
1431
|
-
vec = Face.NormalAtParameters(face, u=u, v=v)
|
1432
|
-
ev = Topology.TranslateByDirectionDistance(iv, vec, length)
|
1433
|
-
return Edge.ByVertices([iv, ev], tolerance=tolerance, silent=silent)
|
1434
|
-
|
1435
|
-
@staticmethod
|
1436
|
-
def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001) -> topologic.Edge:
|
1437
|
-
"""
|
1438
|
-
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.
|
1439
|
-
|
1440
|
-
Parameters
|
1441
|
-
----------
|
1442
|
-
face : topologic.Face
|
1443
|
-
The input face.
|
1444
|
-
u : float , optional
|
1445
|
-
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
|
1446
|
-
v : float , optional
|
1447
|
-
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
|
1448
|
-
length : float , optional
|
1449
|
-
The desired length of the normal edge. The default is 1.
|
1450
|
-
tolerance : float , optional
|
1451
|
-
The desired tolerance. The default is 0.0001.
|
1452
|
-
|
1453
|
-
Returns
|
1454
|
-
-------
|
1455
|
-
topologic.Edge
|
1456
|
-
The created normal edge to the input face. This is computed at the approximate center of the face.
|
1457
|
-
|
1458
|
-
"""
|
1459
|
-
from topologicpy.Edge import Edge
|
1460
|
-
from topologicpy.Topology import Topology
|
1461
|
-
if not isinstance(face, topologic.Face):
|
1462
|
-
return None
|
1463
|
-
sv = Face.VertexByParameters(face=face, u=u, v=v)
|
1464
|
-
vec = Face.NormalAtParameters(face, u=u, v=v)
|
1465
|
-
ev = Topology.TranslateByDirectionDistance(sv, vec, length)
|
1466
|
-
return Edge.ByVertices([sv, ev], tolerance=tolerance, silent=True)
|
1467
|
-
|
1468
|
-
@staticmethod
|
1469
|
-
def PlaneEquation(face: topologic.Face, mantissa: int = 6) -> dict:
|
1470
|
-
"""
|
1471
|
-
Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
|
1472
|
-
|
1473
|
-
Parameters
|
1474
|
-
----------
|
1475
|
-
face : topologic.Face
|
1476
|
-
The input face.
|
1477
|
-
|
1478
|
-
Returns
|
1479
|
-
-------
|
1480
|
-
dict
|
1481
|
-
The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].
|
1482
|
-
|
1483
|
-
"""
|
1484
|
-
from topologicpy.Topology import Topology
|
1485
|
-
from topologicpy.Vertex import Vertex
|
1486
|
-
import random
|
1487
|
-
import time
|
1488
|
-
|
1489
|
-
if not isinstance(face, topologic.Face):
|
1490
|
-
print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.")
|
1491
|
-
return None
|
1492
|
-
vertices = Topology.Vertices(face)
|
1493
|
-
if len(vertices) < 3:
|
1494
|
-
print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.")
|
1495
|
-
return None
|
1496
|
-
return Vertex.PlaneEquation(vertices, mantissa=mantissa)
|
1497
|
-
|
1498
|
-
@staticmethod
|
1499
|
-
def Planarize(face: topologic.Face, origin: topologic.Vertex = None,
|
1500
|
-
tolerance: float = 0.0001) -> topologic.Face:
|
1501
|
-
"""
|
1502
|
-
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.
|
1503
|
-
|
1504
|
-
Parameters
|
1505
|
-
----------
|
1506
|
-
face : topologic.Face
|
1507
|
-
The input face.
|
1508
|
-
origin : topologic.Vertex , optional
|
1509
|
-
The desired vertex to use as the origin of the plane to project the face unto. If set to None, the centroidof the input face is used. The default is None.
|
1510
|
-
tolerance : float , optional
|
1511
|
-
The desired tolerance. The default is 0.0001.
|
1512
|
-
|
1513
|
-
Returns
|
1514
|
-
-------
|
1515
|
-
topologic.Face
|
1516
|
-
The planarized face.
|
1517
|
-
|
1518
|
-
"""
|
1519
|
-
|
1520
|
-
from topologicpy.Wire import Wire
|
1521
|
-
from topologicpy.Topology import Topology
|
1522
|
-
|
1523
|
-
if not isinstance(face, topologic.Face):
|
1524
|
-
return None
|
1525
|
-
if not isinstance(origin, topologic.Vertex):
|
1526
|
-
origin = Topology.Centroid(face)
|
1527
|
-
eb = Face.ExternalBoundary(face)
|
1528
|
-
plan_eb = Wire.Planarize(eb, origin=origin)
|
1529
|
-
ib_list = Face.InternalBoundaries(face)
|
1530
|
-
plan_ib_list = []
|
1531
|
-
for ib in ib_list:
|
1532
|
-
plan_ib_list.append(Wire.Planarize(ib, origin=origin))
|
1533
|
-
plan_face = Face.ByWires(plan_eb, plan_ib_list)
|
1534
|
-
return plan_face
|
1535
|
-
|
1536
|
-
@staticmethod
|
1537
|
-
def Project(faceA: topologic.Face, faceB: topologic.Face, direction : list = None,
|
1538
|
-
mantissa: int = 6, tolerance: float = 0.0001) -> topologic.Face:
|
1539
|
-
"""
|
1540
|
-
Creates a projection of the first input face unto the second input face.
|
1541
|
-
|
1542
|
-
Parameters
|
1543
|
-
----------
|
1544
|
-
faceA : topologic.Face
|
1545
|
-
The face to be projected.
|
1546
|
-
faceB : topologic.Face
|
1547
|
-
The face unto which the first input face will be projected.
|
1548
|
-
direction : list, optional
|
1549
|
-
The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
|
1550
|
-
mantissa : int , optional
|
1551
|
-
The desired length of the mantissa. The default is 6.
|
1552
|
-
tolerance : float , optional
|
1553
|
-
The desired tolerance. The default is 0.0001.
|
1554
|
-
|
1555
|
-
Returns
|
1556
|
-
-------
|
1557
|
-
topologic.Face
|
1558
|
-
The projected Face.
|
1559
|
-
|
1560
|
-
"""
|
1561
|
-
|
1562
|
-
from topologicpy.Wire import Wire
|
1563
|
-
|
1564
|
-
if not faceA:
|
1565
|
-
return None
|
1566
|
-
if not isinstance(faceA, topologic.Face):
|
1567
|
-
return None
|
1568
|
-
if not faceB:
|
1569
|
-
return None
|
1570
|
-
if not isinstance(faceB, topologic.Face):
|
1571
|
-
return None
|
1572
|
-
|
1573
|
-
eb = faceA.ExternalBoundary()
|
1574
|
-
ib_list = []
|
1575
|
-
_ = faceA.InternalBoundaries(ib_list)
|
1576
|
-
p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance)
|
1577
|
-
p_ib_list = []
|
1578
|
-
for ib in ib_list:
|
1579
|
-
temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance)
|
1580
|
-
if temp_ib:
|
1581
|
-
p_ib_list.append(temp_ib)
|
1582
|
-
return Face.ByWires(p_eb, p_ib_list, tolerance=tolerance)
|
1583
|
-
|
1584
|
-
@staticmethod
|
1585
|
-
def RectangleByPlaneEquation(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, placement: str = "center", equation: dict = None, tolerance: float = 0.0001) -> topologic.Face:
|
1586
|
-
from topologicpy.Vertex import Vertex
|
1587
|
-
# Extract coefficients of the plane equation
|
1588
|
-
a = equation['a']
|
1589
|
-
b = equation['b']
|
1590
|
-
c = equation['c']
|
1591
|
-
d = equation['d']
|
1592
|
-
|
1593
|
-
# Calculate the normal vector of the plane
|
1594
|
-
direction = np.array([a, b, c], dtype=float)
|
1595
|
-
direction /= np.linalg.norm(direction)
|
1596
|
-
direction = [x for x in direction]
|
1597
|
-
|
1598
|
-
return Face.Rectangle(origin=origin, width=width, length=length, direction = direction, placement=placement, tolerance=tolerance)
|
1599
|
-
|
1600
|
-
@staticmethod
|
1601
|
-
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:
|
1602
|
-
"""
|
1603
|
-
Creates a rectangle.
|
1604
|
-
|
1605
|
-
Parameters
|
1606
|
-
----------
|
1607
|
-
origin : topologic.Vertex, optional
|
1608
|
-
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0).
|
1609
|
-
width : float , optional
|
1610
|
-
The width of the rectangle. The default is 1.0.
|
1611
|
-
length : float , optional
|
1612
|
-
The length of the rectangle. The default is 1.0.
|
1613
|
-
direction : list , optional
|
1614
|
-
The vector representing the up direction of the rectangle. The default is [0, 0, 1].
|
1615
|
-
placement : str , optional
|
1616
|
-
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".
|
1617
|
-
tolerance : float , optional
|
1618
|
-
The desired tolerance. The default is 0.0001.
|
1619
|
-
|
1620
|
-
Returns
|
1621
|
-
-------
|
1622
|
-
topologic.Face
|
1623
|
-
The created face.
|
1624
|
-
|
1625
|
-
"""
|
1626
|
-
from topologicpy.Wire import Wire
|
1627
|
-
|
1628
|
-
wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
|
1629
|
-
if not isinstance(wire, topologic.Wire):
|
1630
|
-
print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.")
|
1631
|
-
return None
|
1632
|
-
return Face.ByWire(wire, tolerance=tolerance)
|
1633
|
-
|
1634
|
-
@staticmethod
|
1635
|
-
def RemoveCollinearEdges(face: topologic.Face, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
|
1636
|
-
"""
|
1637
|
-
Removes any collinear edges in the input face.
|
1638
|
-
|
1639
|
-
Parameters
|
1640
|
-
----------
|
1641
|
-
face : topologic.Face
|
1642
|
-
The input face.
|
1643
|
-
angTolerance : float , optional
|
1644
|
-
The desired angular tolerance. The default is 0.1.
|
1645
|
-
tolerance : float , optional
|
1646
|
-
The desired tolerance. The default is 0.0001.
|
1647
|
-
|
1648
|
-
Returns
|
1649
|
-
-------
|
1650
|
-
topologic.Face
|
1651
|
-
The created face without any collinear edges.
|
1652
|
-
|
1653
|
-
"""
|
1654
|
-
from topologicpy.Wire import Wire
|
1655
|
-
|
1656
|
-
if not isinstance(face, topologic.Face):
|
1657
|
-
print("Face.RemoveCollinearEdges - Error: The input face parameter is not a valid face. Returning None.")
|
1658
|
-
return None
|
1659
|
-
eb = Wire.RemoveCollinearEdges(Face.Wire(face), angTolerance=angTolerance, tolerance=tolerance)
|
1660
|
-
ib = [Wire.RemoveCollinearEdges(w, angTolerance=angTolerance, tolerance=tolerance) for w in Face.InternalBoundaries(face)]
|
1661
|
-
return Face.ByWires(eb, ib)
|
1662
|
-
|
1663
|
-
@staticmethod
|
1664
|
-
def Skeleton(face, tolerance=0.001):
|
1665
|
-
"""
|
1666
|
-
Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1667
|
-
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1668
|
-
|
1669
|
-
Parameters
|
1670
|
-
----------
|
1671
|
-
face : topologic.Face
|
1672
|
-
The input face.
|
1673
|
-
tolerance : float , optional
|
1674
|
-
The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better)
|
1675
|
-
|
1676
|
-
Returns
|
1677
|
-
-------
|
1678
|
-
topologic.Wire
|
1679
|
-
The created straight skeleton.
|
1680
|
-
|
1681
|
-
"""
|
1682
|
-
from topologicpy.Wire import Wire
|
1683
|
-
if not isinstance(face, topologic.Face):
|
1684
|
-
print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
|
1685
|
-
return None
|
1686
|
-
return Wire.Skeleton(face, tolerance=tolerance)
|
1687
|
-
|
1688
|
-
@staticmethod
|
1689
|
-
def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
1690
|
-
"""
|
1691
|
-
Creates a square.
|
1692
|
-
|
1693
|
-
Parameters
|
1694
|
-
----------
|
1695
|
-
origin : topologic.Vertex , optional
|
1696
|
-
The location of the origin of the square. The default is None which results in the square being placed at (0, 0, 0).
|
1697
|
-
size : float , optional
|
1698
|
-
The size of the square. The default is 1.0.
|
1699
|
-
direction : list , optional
|
1700
|
-
The vector representing the up direction of the square. The default is [0, 0, 1].
|
1701
|
-
placement : str , optional
|
1702
|
-
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".
|
1703
|
-
tolerance : float , optional
|
1704
|
-
The desired tolerance. The default is 0.0001.
|
1705
|
-
|
1706
|
-
Returns
|
1707
|
-
-------
|
1708
|
-
topologic.Face
|
1709
|
-
The created square.
|
1710
|
-
|
1711
|
-
"""
|
1712
|
-
return Face.Rectangle(origin=origin, width=size, length=size, direction=direction, placement=placement, tolerance=tolerance)
|
1713
|
-
|
1714
|
-
@staticmethod
|
1715
|
-
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:
|
1716
|
-
"""
|
1717
|
-
Creates a star.
|
1718
|
-
|
1719
|
-
Parameters
|
1720
|
-
----------
|
1721
|
-
origin : topologic.Vertex, optional
|
1722
|
-
The location of the origin of the star. The default is None which results in the star being placed at (0, 0, 0).
|
1723
|
-
radiusA : float , optional
|
1724
|
-
The outer radius of the star. The default is 1.0.
|
1725
|
-
radiusB : float , optional
|
1726
|
-
The outer radius of the star. The default is 0.4.
|
1727
|
-
rays : int , optional
|
1728
|
-
The number of star rays. The default is 5.
|
1729
|
-
direction : list , optional
|
1730
|
-
The vector representing the up direction of the star. The default is [0, 0, 1].
|
1731
|
-
placement : str , optional
|
1732
|
-
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".
|
1733
|
-
tolerance : float , optional
|
1734
|
-
The desired tolerance. The default is 0.0001.
|
1735
|
-
|
1736
|
-
Returns
|
1737
|
-
-------
|
1738
|
-
topologic.Face
|
1739
|
-
The created face.
|
1740
|
-
|
1741
|
-
"""
|
1742
|
-
from topologicpy.Wire import Wire
|
1743
|
-
wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
|
1744
|
-
if not isinstance(wire, topologic.Wire):
|
1745
|
-
print("Face.Rectangle - Error: Could not create the base wire for the star. Returning None.")
|
1746
|
-
return None
|
1747
|
-
return Face.ByWire(wire, tolerance=tolerance)
|
1748
|
-
|
1749
|
-
@staticmethod
|
1750
|
-
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:
|
1751
|
-
"""
|
1752
|
-
Creates a trapezoid.
|
1753
|
-
|
1754
|
-
Parameters
|
1755
|
-
----------
|
1756
|
-
origin : topologic.Vertex, optional
|
1757
|
-
The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0, 0, 0).
|
1758
|
-
widthA : float , optional
|
1759
|
-
The width of the bottom edge of the trapezoid. The default is 1.0.
|
1760
|
-
widthB : float , optional
|
1761
|
-
The width of the top edge of the trapezoid. The default is 0.75.
|
1762
|
-
offsetA : float , optional
|
1763
|
-
The offset of the bottom edge of the trapezoid. The default is 0.0.
|
1764
|
-
offsetB : float , optional
|
1765
|
-
The offset of the top edge of the trapezoid. The default is 0.0.
|
1766
|
-
length : float , optional
|
1767
|
-
The length of the trapezoid. The default is 1.0.
|
1768
|
-
direction : list , optional
|
1769
|
-
The vector representing the up direction of the trapezoid. The default is [0, 0, 1].
|
1770
|
-
placement : str , optional
|
1771
|
-
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".
|
1772
|
-
tolerance : float , optional
|
1773
|
-
The desired tolerance. The default is 0.0001.
|
1774
|
-
|
1775
|
-
Returns
|
1776
|
-
-------
|
1777
|
-
topologic.Face
|
1778
|
-
The created trapezoid.
|
1779
|
-
|
1780
|
-
"""
|
1781
|
-
from topologicpy.Wire import Wire
|
1782
|
-
wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
|
1783
|
-
if not isinstance(wire, topologic.Wire):
|
1784
|
-
print("Face.Rectangle - Error: Could not create the base wire for the trapezoid. Returning None.")
|
1785
|
-
return None
|
1786
|
-
return Face.ByWire(wire, tolerance=tolerance)
|
1787
|
-
|
1788
|
-
@staticmethod
|
1789
|
-
def Triangulate(face:topologic.Face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) -> list:
|
1790
|
-
"""
|
1791
|
-
Triangulates the input face and returns a list of faces.
|
1792
|
-
|
1793
|
-
Parameters
|
1794
|
-
----------
|
1795
|
-
face : topologic.Face
|
1796
|
-
The input face.
|
1797
|
-
tolerance : float , optional
|
1798
|
-
The desired tolerance. The default is 0.0001.
|
1799
|
-
mode : int , optional
|
1800
|
-
The desired mode of meshing algorithm. Several options are available:
|
1801
|
-
0: Classic
|
1802
|
-
1: MeshAdapt
|
1803
|
-
3: Initial Mesh Only
|
1804
|
-
5: Delaunay
|
1805
|
-
6: Frontal-Delaunay
|
1806
|
-
7: BAMG
|
1807
|
-
8: Fontal-Delaunay for Quads
|
1808
|
-
9: Packing of Parallelograms
|
1809
|
-
All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
|
1810
|
-
WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
|
1811
|
-
meshSize : float , optional
|
1812
|
-
The desired size of the mesh when using the "mesh" option. If set to None, it will be
|
1813
|
-
calculated automatically and set to 10% of the overall size of the face.
|
1814
|
-
|
1815
|
-
Returns
|
1816
|
-
-------
|
1817
|
-
list
|
1818
|
-
The list of triangles of the input face.
|
1819
|
-
|
1820
|
-
"""
|
1821
|
-
from topologicpy.Wire import Wire
|
1822
|
-
from topologicpy.Topology import Topology
|
1823
|
-
|
1824
|
-
# This function was contributed by Yidan Xue.
|
1825
|
-
def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001):
|
1826
|
-
"""
|
1827
|
-
Creates a gmsh of triangular meshes from the input face.
|
1828
|
-
|
1829
|
-
Parameters
|
1830
|
-
----------
|
1831
|
-
face : topologic.Face
|
1832
|
-
The input face.
|
1833
|
-
meshSize : float , optional
|
1834
|
-
The desired mesh size.
|
1835
|
-
tolerance : float , optional
|
1836
|
-
The desired tolerance. The default is 0.0001.
|
1837
|
-
|
1838
|
-
Returns
|
1839
|
-
----------
|
1840
|
-
topologic.Shell
|
1841
|
-
The shell of triangular meshes.
|
1842
|
-
|
1843
|
-
"""
|
1844
|
-
import os
|
1845
|
-
import warnings
|
1846
|
-
try:
|
1847
|
-
import numpy as np
|
1848
|
-
except:
|
1849
|
-
print("Face.Triangulate - Warning: Installing required numpy library.")
|
1850
|
-
try:
|
1851
|
-
os.system("pip install numpy")
|
1852
|
-
except:
|
1853
|
-
os.system("pip install numpy --user")
|
1854
|
-
try:
|
1855
|
-
import numpy as np
|
1856
|
-
print("Face.Triangulate - Warning: numpy library installed correctly.")
|
1857
|
-
except:
|
1858
|
-
warnings.warn("Face.Triangulate - Error: Could not import numpy. Please try to install numpy manually. Returning None.")
|
1859
|
-
return None
|
1860
|
-
try:
|
1861
|
-
import gmsh
|
1862
|
-
except:
|
1863
|
-
print("Face.Triangulate - Warning: Installing required gmsh library.")
|
1864
|
-
try:
|
1865
|
-
os.system("pip install gmsh")
|
1866
|
-
except:
|
1867
|
-
os.system("pip install gmsh --user")
|
1868
|
-
try:
|
1869
|
-
import gmsh
|
1870
|
-
print("Face.Triangulate - Warning: gmsh library installed correctly.")
|
1871
|
-
except:
|
1872
|
-
warnings.warn("Face.Triangulate - Error: Could not import gmsh. Please try to install gmsh manually. Returning None.")
|
1873
|
-
return None
|
1874
|
-
|
1875
|
-
import topologic
|
1876
|
-
from topologicpy.Vertex import Vertex
|
1877
|
-
from topologicpy.Wire import Wire
|
1878
|
-
from topologicpy.Face import Face
|
1879
|
-
|
1880
|
-
if not isinstance(face, topologic.Face):
|
1881
|
-
print("Shell.ByMeshFace - Error: The input face parameter is not a valid face. Returning None.")
|
1882
|
-
return None
|
1883
|
-
if not meshSize:
|
1884
|
-
bounding_face = Face.BoundingRectangle(face)
|
1885
|
-
bounding_face_vertices = Face.Vertices(bounding_face)
|
1886
|
-
bounding_face_vertices_x = [Vertex.X(i) for i in bounding_face_vertices]
|
1887
|
-
bounding_face_vertices_y = [Vertex.Y(i) for i in bounding_face_vertices]
|
1888
|
-
width = max(bounding_face_vertices_x)-min(bounding_face_vertices_x)
|
1889
|
-
length = max(bounding_face_vertices_y)-min(bounding_face_vertices_y)
|
1890
|
-
meshSize = max([width,length])//10
|
1891
|
-
|
1892
|
-
gmsh.initialize()
|
1893
|
-
face_external_boundary = Face.ExternalBoundary(face)
|
1894
|
-
external_vertices = Wire.Vertices(face_external_boundary)
|
1895
|
-
external_vertex_number = len(external_vertices)
|
1896
|
-
for i in range(external_vertex_number):
|
1897
|
-
gmsh.model.geo.addPoint(Vertex.X(external_vertices[i]), Vertex.Y(external_vertices[i]), Vertex.Z(external_vertices[i]), meshSize, i+1)
|
1898
|
-
for i in range(external_vertex_number):
|
1899
|
-
if i < external_vertex_number-1:
|
1900
|
-
gmsh.model.geo.addLine(i+1, i+2, i+1)
|
1901
|
-
else:
|
1902
|
-
gmsh.model.geo.addLine(i+1, 1, i+1)
|
1903
|
-
gmsh.model.geo.addCurveLoop([i+1 for i in range(external_vertex_number)], 1)
|
1904
|
-
current_vertex_number = external_vertex_number
|
1905
|
-
current_edge_number = external_vertex_number
|
1906
|
-
current_wire_number = 1
|
1907
|
-
|
1908
|
-
face_internal_boundaries = Face.InternalBoundaries(face)
|
1909
|
-
if face_internal_boundaries:
|
1910
|
-
internal_face_number = len(face_internal_boundaries)
|
1911
|
-
for i in range(internal_face_number):
|
1912
|
-
face_internal_boundary = face_internal_boundaries[i]
|
1913
|
-
internal_vertices = Wire.Vertices(face_internal_boundary)
|
1914
|
-
internal_vertex_number = len(internal_vertices)
|
1915
|
-
for j in range(internal_vertex_number):
|
1916
|
-
gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j]), Vertex.Z(internal_vertices[j]), meshSize, current_vertex_number+j+1)
|
1917
|
-
for j in range(internal_vertex_number):
|
1918
|
-
if j < internal_vertex_number-1:
|
1919
|
-
gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+j+2, current_edge_number+j+1)
|
1920
|
-
else:
|
1921
|
-
gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+1, current_edge_number+j+1)
|
1922
|
-
gmsh.model.geo.addCurveLoop([current_edge_number+i+1 for i in range(internal_vertex_number)], current_wire_number+1)
|
1923
|
-
current_vertex_number = current_vertex_number+internal_vertex_number
|
1924
|
-
current_edge_number = current_edge_number+internal_vertex_number
|
1925
|
-
current_wire_number = current_wire_number+1
|
1926
|
-
|
1927
|
-
gmsh.model.geo.addPlaneSurface([i+1 for i in range(current_wire_number)])
|
1928
|
-
gmsh.model.geo.synchronize()
|
1929
|
-
if mode not in [1,3,5,6,7,8,9]:
|
1930
|
-
mode = 6
|
1931
|
-
gmsh.option.setNumber("Mesh.Algorithm", mode)
|
1932
|
-
gmsh.model.mesh.generate(2) # For a 2D mesh
|
1933
|
-
nodeTags, nodeCoords, nodeParams = gmsh.model.mesh.getNodes(-1, -1)
|
1934
|
-
elemTypes, elemTags, elemNodeTags = gmsh.model.mesh.getElements(-1, -1)
|
1935
|
-
gmsh.finalize()
|
1936
|
-
|
1937
|
-
vertex_number = len(nodeTags)
|
1938
|
-
vertices = []
|
1939
|
-
for i in range(vertex_number):
|
1940
|
-
vertices.append(Vertex.ByCoordinates(nodeCoords[3*i],nodeCoords[3*i+1],nodeCoords[3*i+2]))
|
1941
|
-
|
1942
|
-
faces = []
|
1943
|
-
for n in range(len(elemTypes)):
|
1944
|
-
vn = elemTypes[n]+1
|
1945
|
-
et = elemTags[n]
|
1946
|
-
ent = elemNodeTags[n]
|
1947
|
-
if vn==3:
|
1948
|
-
for i in range(len(et)):
|
1949
|
-
face_vertices = []
|
1950
|
-
for j in range(vn):
|
1951
|
-
face_vertices.append(vertices[np.where(nodeTags==ent[i*vn+j])[0][0]])
|
1952
|
-
faces.append(Face.ByVertices(face_vertices))
|
1953
|
-
return faces
|
1954
|
-
|
1955
|
-
if not isinstance(face, topologic.Face):
|
1956
|
-
print("Face.Triangulate - Error: The input face parameter is not a valid face. Returning None.")
|
1957
|
-
return None
|
1958
|
-
vertices = Topology.Vertices(face)
|
1959
|
-
if len(vertices) == 3: # Already a triangle
|
1960
|
-
return [face]
|
1961
|
-
origin = Topology.Centroid(face)
|
1962
|
-
normal = Face.Normal(face)
|
1963
|
-
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1964
|
-
|
1965
|
-
if mode == 0:
|
1966
|
-
shell_faces = []
|
1967
|
-
for i in range(0,5,1):
|
1968
|
-
try:
|
1969
|
-
_ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, shell_faces)
|
1970
|
-
break
|
1971
|
-
except:
|
1972
|
-
continue
|
1973
|
-
else:
|
1974
|
-
shell_faces = generate_gmsh(flatFace, mode = mode, meshSize = meshSize, tolerance = tolerance)
|
1975
|
-
|
1976
|
-
if len(shell_faces) < 1:
|
1977
|
-
return []
|
1978
|
-
finalFaces = []
|
1979
|
-
for f in shell_faces:
|
1980
|
-
f = Topology.Unflatten(f, origin=origin, direction=normal)
|
1981
|
-
if Face.Angle(face, f) > 90:
|
1982
|
-
wire = Face.ExternalBoundary(f)
|
1983
|
-
wire = Wire.Invert(wire)
|
1984
|
-
f = topologic.Face.ByExternalBoundary(wire)
|
1985
|
-
finalFaces.append(f)
|
1986
|
-
else:
|
1987
|
-
finalFaces.append(f)
|
1988
|
-
return finalFaces
|
1989
|
-
|
1990
|
-
@staticmethod
|
1991
|
-
def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) -> topologic.Face:
|
1992
|
-
"""
|
1993
|
-
Trims the input face by the input wire.
|
1994
|
-
|
1995
|
-
Parameters
|
1996
|
-
----------
|
1997
|
-
face : topologic.Face
|
1998
|
-
The input face.
|
1999
|
-
wire : topologic.Wire
|
2000
|
-
The input wire.
|
2001
|
-
reverse : bool , optional
|
2002
|
-
If set to True, the effect of the trim will be reversed. The default is False.
|
2003
|
-
|
2004
|
-
Returns
|
2005
|
-
-------
|
2006
|
-
topologic.Face
|
2007
|
-
The resulting trimmed face.
|
2008
|
-
|
2009
|
-
"""
|
2010
|
-
if not isinstance(face, topologic.Face):
|
2011
|
-
return None
|
2012
|
-
if not isinstance(wire, topologic.Wire):
|
2013
|
-
return face
|
2014
|
-
trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False)
|
2015
|
-
if reverse:
|
2016
|
-
trimmed_face = face.Difference(trimmed_face)
|
2017
|
-
return trimmed_face
|
2018
|
-
|
2019
|
-
@staticmethod
|
2020
|
-
def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) -> topologic.Vertex:
|
2021
|
-
"""
|
2022
|
-
Creates a vertex at the *u* and *v* parameters of the input face.
|
2023
|
-
|
2024
|
-
Parameters
|
2025
|
-
----------
|
2026
|
-
face : topologic.Face
|
2027
|
-
The input face.
|
2028
|
-
u : float , optional
|
2029
|
-
The *u* parameter of the input face. The default is 0.5.
|
2030
|
-
v : float , optional
|
2031
|
-
The *v* parameter of the input face. The default is 0.5.
|
2032
|
-
|
2033
|
-
Returns
|
2034
|
-
-------
|
2035
|
-
vertex : topologic vertex
|
2036
|
-
The created vertex.
|
2037
|
-
|
2038
|
-
"""
|
2039
|
-
if not isinstance(face, topologic.Face):
|
2040
|
-
return None
|
2041
|
-
return topologic.FaceUtility.VertexAtParameters(face, u, v)
|
2042
|
-
|
2043
|
-
@staticmethod
|
2044
|
-
def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = "uv", mantissa: int = 6) -> list:
|
2045
|
-
"""
|
2046
|
-
Returns the *u* and *v* parameters of the input face at the location of the input vertex.
|
2047
|
-
|
2048
|
-
Parameters
|
2049
|
-
----------
|
2050
|
-
face : topologic.Face
|
2051
|
-
The input face.
|
2052
|
-
vertex : topologic.Vertex
|
2053
|
-
The input vertex.
|
2054
|
-
outputType : string , optional
|
2055
|
-
The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
|
2056
|
-
mantissa : int , optional
|
2057
|
-
The desired length of the mantissa. The default is 6.
|
2058
|
-
|
2059
|
-
Returns
|
2060
|
-
-------
|
2061
|
-
list
|
2062
|
-
The list of *u* and/or *v* as specified by the outputType input.
|
2063
|
-
|
2064
|
-
"""
|
2065
|
-
if not isinstance(face, topologic.Face):
|
2066
|
-
return None
|
2067
|
-
if not isinstance(vertex, topologic.Vertex):
|
2068
|
-
return None
|
2069
|
-
params = topologic.FaceUtility.ParametersAtVertex(face, vertex)
|
2070
|
-
u = round(params[0], mantissa)
|
2071
|
-
v = round(params[1], mantissa)
|
2072
|
-
outputType = list(outputType.lower())
|
2073
|
-
returnResult = []
|
2074
|
-
for param in outputType:
|
2075
|
-
if param == "u":
|
2076
|
-
returnResult.append(u)
|
2077
|
-
elif param == "v":
|
2078
|
-
returnResult.append(v)
|
2079
|
-
return returnResult
|
2080
|
-
|
2081
|
-
@staticmethod
|
2082
|
-
def Vertices(face: topologic.Face) -> list:
|
2083
|
-
"""
|
2084
|
-
Returns the vertices of the input face.
|
2085
|
-
|
2086
|
-
Parameters
|
2087
|
-
----------
|
2088
|
-
face : topologic.Face
|
2089
|
-
The input face.
|
2090
|
-
|
2091
|
-
Returns
|
2092
|
-
-------
|
2093
|
-
list
|
2094
|
-
The list of vertices.
|
2095
|
-
|
2096
|
-
"""
|
2097
|
-
if not isinstance(face, topologic.Face):
|
2098
|
-
return None
|
2099
|
-
vertices = []
|
2100
|
-
_ = face.Vertices(None, vertices)
|
2101
|
-
return vertices
|
2102
|
-
|
2103
|
-
@staticmethod
|
2104
|
-
def Wire(face: topologic.Face) -> topologic.Wire:
|
2105
|
-
"""
|
2106
|
-
Returns the external boundary (closed wire) of the input face.
|
2107
|
-
|
2108
|
-
Parameters
|
2109
|
-
----------
|
2110
|
-
face : topologic.Face
|
2111
|
-
The input face.
|
2112
|
-
|
2113
|
-
Returns
|
2114
|
-
-------
|
2115
|
-
topologic.Wire
|
2116
|
-
The external boundary of the input face.
|
2117
|
-
|
2118
|
-
"""
|
2119
|
-
return face.ExternalBoundary()
|
2120
|
-
|
2121
|
-
@staticmethod
|
2122
|
-
def Wires(face: topologic.Face) -> list:
|
2123
|
-
"""
|
2124
|
-
Returns the wires of the input face.
|
2125
|
-
|
2126
|
-
Parameters
|
2127
|
-
----------
|
2128
|
-
face : topologic.Face
|
2129
|
-
The input face.
|
2130
|
-
|
2131
|
-
Returns
|
2132
|
-
-------
|
2133
|
-
list
|
2134
|
-
The list of wires.
|
2135
|
-
|
2136
|
-
"""
|
2137
|
-
if not isinstance(face, topologic.Face):
|
2138
|
-
return None
|
2139
|
-
wires = []
|
2140
|
-
_ = face.Wires(None, wires)
|
2141
|
-
return wires
|
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.Vector import Vector
|
19
|
+
from topologicpy.Wire import Wire
|
20
|
+
from topologicpy.Topology import Topology
|
21
|
+
import math
|
22
|
+
import os
|
23
|
+
import warnings
|
24
|
+
|
25
|
+
try:
|
26
|
+
import numpy as np
|
27
|
+
except:
|
28
|
+
print("Face - Installing required numpy library.")
|
29
|
+
try:
|
30
|
+
os.system("pip install numpy")
|
31
|
+
except:
|
32
|
+
os.system("pip install numpy --user")
|
33
|
+
try:
|
34
|
+
import numpy as np
|
35
|
+
print("Face - numpy library installed correctly.")
|
36
|
+
except:
|
37
|
+
warnings.warn("Face - Error: Could not import numpy.")
|
38
|
+
|
39
|
+
class Face(Topology):
|
40
|
+
@staticmethod
|
41
|
+
def AddInternalBoundaries(face: topologic.Face, wires: list) -> topologic.Face:
|
42
|
+
"""
|
43
|
+
Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
|
44
|
+
|
45
|
+
Parameters
|
46
|
+
----------
|
47
|
+
face : topologic.Face
|
48
|
+
The input face.
|
49
|
+
wires : list
|
50
|
+
The input list of internal boundaries (closed wires).
|
51
|
+
|
52
|
+
Returns
|
53
|
+
-------
|
54
|
+
topologic.Face
|
55
|
+
The created face with internal boundaries added to it.
|
56
|
+
|
57
|
+
"""
|
58
|
+
|
59
|
+
if not isinstance(face, topologic.Face):
|
60
|
+
print("Face.AddInternalBoundaries - Error: The input face parameter is not a valid topologic face. Returning None.")
|
61
|
+
return None
|
62
|
+
if not isinstance(wires, list):
|
63
|
+
print("Face.AddInternalBoundaries - Warning: The input wires parameter is not a valid list. Returning the input face.")
|
64
|
+
return face
|
65
|
+
wireList = [w for w in wires if isinstance(w, topologic.Wire)]
|
66
|
+
if len(wireList) < 1:
|
67
|
+
print("Face.AddInternalBoundaries - Warning: The input wires parameter does not contain any valid wires. Returning the input face.")
|
68
|
+
return face
|
69
|
+
faceeb = face.ExternalBoundary()
|
70
|
+
faceibList = []
|
71
|
+
_ = face.InternalBoundaries(faceibList)
|
72
|
+
for wire in wires:
|
73
|
+
faceibList.append(wire)
|
74
|
+
return topologic.Face.ByExternalInternalBoundaries(faceeb, faceibList)
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def AddInternalBoundariesCluster(face: topologic.Face, cluster: topologic.Cluster) -> topologic.Face:
|
78
|
+
"""
|
79
|
+
Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
|
80
|
+
|
81
|
+
Parameters
|
82
|
+
----------
|
83
|
+
face : topologic.Face
|
84
|
+
The input face.
|
85
|
+
cluster : topologic.Cluster
|
86
|
+
The input cluster of internal boundaries (topologic wires).
|
87
|
+
|
88
|
+
Returns
|
89
|
+
-------
|
90
|
+
topologic.Face
|
91
|
+
The created face with internal boundaries added to it.
|
92
|
+
|
93
|
+
"""
|
94
|
+
if not isinstance(face, topologic.Face):
|
95
|
+
print("Face.AddInternalBoundariesCluster - Warning: The input cluster parameter is not a valid cluster. Returning None.")
|
96
|
+
return None
|
97
|
+
if not cluster:
|
98
|
+
return face
|
99
|
+
if not isinstance(cluster, topologic.Cluster):
|
100
|
+
return face
|
101
|
+
wires = []
|
102
|
+
_ = cluster.Wires(None, wires)
|
103
|
+
return Face.AddInternalBoundaries(face, wires)
|
104
|
+
|
105
|
+
@staticmethod
|
106
|
+
def Angle(faceA: topologic.Face, faceB: topologic.Face, mantissa: int = 6) -> float:
|
107
|
+
"""
|
108
|
+
Returns the angle in degrees between the two input faces.
|
109
|
+
|
110
|
+
Parameters
|
111
|
+
----------
|
112
|
+
faceA : topologic.Face
|
113
|
+
The first input face.
|
114
|
+
faceB : topologic.Face
|
115
|
+
The second input face.
|
116
|
+
mantissa : int , optional
|
117
|
+
The desired length of the mantissa. The default is 6.
|
118
|
+
|
119
|
+
Returns
|
120
|
+
-------
|
121
|
+
float
|
122
|
+
The angle in degrees between the two input faces.
|
123
|
+
|
124
|
+
"""
|
125
|
+
from topologicpy.Vector import Vector
|
126
|
+
if not isinstance(faceA, topologic.Face):
|
127
|
+
print("Face.Angle - Warning: The input faceA parameter is not a valid topologic face. Returning None.")
|
128
|
+
return None
|
129
|
+
if not isinstance(faceB, topologic.Face):
|
130
|
+
print("Face.Angle - Warning: The input faceB parameter is not a valid topologic face. Returning None.")
|
131
|
+
return None
|
132
|
+
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
|
133
|
+
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
|
134
|
+
return round((Vector.Angle(dirA, dirB)), mantissa)
|
135
|
+
|
136
|
+
@staticmethod
|
137
|
+
def Area(face: topologic.Face, mantissa: int = 6) -> float:
|
138
|
+
"""
|
139
|
+
Returns the area of the input face.
|
140
|
+
|
141
|
+
Parameters
|
142
|
+
----------
|
143
|
+
face : topologic.Face
|
144
|
+
The input face.
|
145
|
+
mantissa : int , optional
|
146
|
+
The desired length of the mantissa. The default is 6.
|
147
|
+
|
148
|
+
Returns
|
149
|
+
-------
|
150
|
+
float
|
151
|
+
The area of the input face.
|
152
|
+
|
153
|
+
"""
|
154
|
+
if not isinstance(face, topologic.Face):
|
155
|
+
print("Face.Area - Warning: The input face parameter is not a valid topologic face. Returning None.")
|
156
|
+
return None
|
157
|
+
area = None
|
158
|
+
try:
|
159
|
+
area = round(topologic.FaceUtility.Area(face), mantissa)
|
160
|
+
except:
|
161
|
+
area = None
|
162
|
+
return area
|
163
|
+
|
164
|
+
@staticmethod
|
165
|
+
def BoundingRectangle(topology: topologic.Topology, optimize: int = 0, tolerance: float = 0.0001) -> topologic.Face:
|
166
|
+
"""
|
167
|
+
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.
|
168
|
+
|
169
|
+
Parameters
|
170
|
+
----------
|
171
|
+
topology : topologic.Topology
|
172
|
+
The input topology.
|
173
|
+
optimize : int , optional
|
174
|
+
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.
|
175
|
+
tolerance : float , optional
|
176
|
+
The desired tolerance. The default is 0.0001.
|
177
|
+
|
178
|
+
Returns
|
179
|
+
-------
|
180
|
+
topologic.Face
|
181
|
+
The bounding rectangle of the input topology.
|
182
|
+
|
183
|
+
"""
|
184
|
+
from topologicpy.Wire import Wire
|
185
|
+
from topologicpy.Face import Face
|
186
|
+
from topologicpy.Cluster import Cluster
|
187
|
+
from topologicpy.Topology import Topology
|
188
|
+
from topologicpy.Dictionary import Dictionary
|
189
|
+
def bb(topology):
|
190
|
+
vertices = []
|
191
|
+
_ = topology.Vertices(None, vertices)
|
192
|
+
x = []
|
193
|
+
y = []
|
194
|
+
for aVertex in vertices:
|
195
|
+
x.append(aVertex.X())
|
196
|
+
y.append(aVertex.Y())
|
197
|
+
minX = min(x)
|
198
|
+
minY = min(y)
|
199
|
+
maxX = max(x)
|
200
|
+
maxY = max(y)
|
201
|
+
return [minX, minY, maxX, maxY]
|
202
|
+
|
203
|
+
if not isinstance(topology, topologic.Topology):
|
204
|
+
print("Face.BoundingRectangle - Warning: The input topology parameter is not a valid topologic topology. Returning None.")
|
205
|
+
return None
|
206
|
+
vertices = Topology.SubTopologies(topology, subTopologyType="vertex")
|
207
|
+
topology = Cluster.ByTopologies(vertices)
|
208
|
+
boundingBox = bb(topology)
|
209
|
+
minX = boundingBox[0]
|
210
|
+
minY = boundingBox[1]
|
211
|
+
maxX = boundingBox[2]
|
212
|
+
maxY = boundingBox[3]
|
213
|
+
w = abs(maxX - minX)
|
214
|
+
l = abs(maxY - minY)
|
215
|
+
best_area = l*w
|
216
|
+
orig_area = best_area
|
217
|
+
best_z = 0
|
218
|
+
best_bb = boundingBox
|
219
|
+
origin = Topology.Centroid(topology)
|
220
|
+
optimize = min(max(optimize, 0), 10)
|
221
|
+
if optimize > 0:
|
222
|
+
factor = (round(((11 - optimize)/30 + 0.57), 2))
|
223
|
+
flag = False
|
224
|
+
for n in range(10,0,-1):
|
225
|
+
if flag:
|
226
|
+
break
|
227
|
+
za = n
|
228
|
+
zb = 90+n
|
229
|
+
zc = n
|
230
|
+
for z in range(za,zb,zc):
|
231
|
+
if flag:
|
232
|
+
break
|
233
|
+
t = Topology.Rotate(topology, origin=origin, axis=[0, 0, 1], angle=z)
|
234
|
+
minX, minY, maxX, maxY = bb(t)
|
235
|
+
w = abs(maxX - minX)
|
236
|
+
l = abs(maxY - minY)
|
237
|
+
area = l*w
|
238
|
+
if area < orig_area*factor:
|
239
|
+
best_area = area
|
240
|
+
best_z = z
|
241
|
+
best_bb = [minX, minY, maxX, maxY]
|
242
|
+
flag = True
|
243
|
+
break
|
244
|
+
if area < best_area:
|
245
|
+
best_area = area
|
246
|
+
best_z = z
|
247
|
+
best_bb = [minX, minY, maxX, maxY]
|
248
|
+
|
249
|
+
else:
|
250
|
+
best_bb = boundingBox
|
251
|
+
|
252
|
+
minX, minY, maxX, maxY = best_bb
|
253
|
+
vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
|
254
|
+
vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
|
255
|
+
vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
|
256
|
+
vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)
|
257
|
+
|
258
|
+
baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
|
259
|
+
baseFace = Face.ByWire(baseWire, tolerance=tolerance)
|
260
|
+
baseFace = Topology.Rotate(baseFace, origin=origin, axis=[0, 0, 1], angle=-best_z)
|
261
|
+
dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
|
262
|
+
baseFace = Topology.SetDictionary(baseFace, dictionary)
|
263
|
+
return baseFace
|
264
|
+
|
265
|
+
@staticmethod
|
266
|
+
def ByEdges(edges: list, tolerance : float = 0.0001) -> topologic.Face:
|
267
|
+
"""
|
268
|
+
Creates a face from the input list of edges.
|
269
|
+
|
270
|
+
Parameters
|
271
|
+
----------
|
272
|
+
edges : list
|
273
|
+
The input list of edges.
|
274
|
+
tolerance : float , optional
|
275
|
+
The desired tolerance. The default is 0.0001.
|
276
|
+
|
277
|
+
Returns
|
278
|
+
-------
|
279
|
+
face : topologic.Face
|
280
|
+
The created face.
|
281
|
+
|
282
|
+
"""
|
283
|
+
from topologicpy.Wire import Wire
|
284
|
+
if not isinstance(edges, list):
|
285
|
+
print("Face.ByEdges - Error: The input edges parameter is not a valid list. Returning None.")
|
286
|
+
return None
|
287
|
+
edges = [e for e in edges if isinstance(e, topologic.Edge)]
|
288
|
+
if len(edges) < 1:
|
289
|
+
print("Face.ByEdges - Error: The input edges parameter does not contain any valid edges. Returning None.")
|
290
|
+
return None
|
291
|
+
wire = Wire.ByEdges(edges, tolerance=tolerance)
|
292
|
+
if not isinstance(wire, topologic.Wire):
|
293
|
+
print("Face.ByEdges - Error: Could not create the required wire. Returning None.")
|
294
|
+
return None
|
295
|
+
return Face.ByWire(wire, tolerance=tolerance)
|
296
|
+
|
297
|
+
@staticmethod
|
298
|
+
def ByEdgesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Face:
|
299
|
+
"""
|
300
|
+
Creates a face from the input cluster of edges.
|
301
|
+
|
302
|
+
Parameters
|
303
|
+
----------
|
304
|
+
cluster : topologic.Cluster
|
305
|
+
The input cluster of edges.
|
306
|
+
tolerance : float , optional
|
307
|
+
The desired tolerance. The default is 0.0001.
|
308
|
+
|
309
|
+
Returns
|
310
|
+
-------
|
311
|
+
face : topologic.Face
|
312
|
+
The created face.
|
313
|
+
|
314
|
+
"""
|
315
|
+
from topologicpy.Cluster import Cluster
|
316
|
+
if not isinstance(cluster, topologic.Cluster):
|
317
|
+
print("Face.ByEdgesCluster - Warning: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
318
|
+
return None
|
319
|
+
edges = Cluster.Edges(cluster)
|
320
|
+
if len(edges) < 1:
|
321
|
+
print("Face.ByEdgesCluster - Warning: The input cluster parameter does not contain any valid edges. Returning None.")
|
322
|
+
return None
|
323
|
+
return Face.ByEdges(edges, tolerance=tolerance)
|
324
|
+
|
325
|
+
@staticmethod
|
326
|
+
def ByOffset(face: topologic.Face, offset: float = 1.0, miter: bool = False,
|
327
|
+
miterThreshold: float = None, offsetKey: str = None,
|
328
|
+
miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001) -> topologic.Face:
|
329
|
+
"""
|
330
|
+
Creates an offset face from the input face.
|
331
|
+
|
332
|
+
Parameters
|
333
|
+
----------
|
334
|
+
face : topologic.Face
|
335
|
+
The input face.
|
336
|
+
offset : float , optional
|
337
|
+
The desired offset distance. The default is 1.0.
|
338
|
+
miter : bool , optional
|
339
|
+
if set to True, the corners will be mitered. The default is False.
|
340
|
+
miterThreshold : float , optional
|
341
|
+
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.
|
342
|
+
offsetKey : str , optional
|
343
|
+
If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
|
344
|
+
miterThresholdKey : str , optional
|
345
|
+
If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
|
346
|
+
step : bool , optional
|
347
|
+
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.
|
348
|
+
tolerance : float , optional
|
349
|
+
The desired tolerance. The default is 0.0001.
|
350
|
+
|
351
|
+
Returns
|
352
|
+
-------
|
353
|
+
topologic.Face
|
354
|
+
The created face.
|
355
|
+
|
356
|
+
"""
|
357
|
+
from topologicpy.Wire import Wire
|
358
|
+
if not isinstance(face, topologic.Face):
|
359
|
+
print("Face.ByOffset - Warning: The input face parameter is not a valid toplogic face. Returning None.")
|
360
|
+
return None
|
361
|
+
eb = Face.Wire(face)
|
362
|
+
internal_boundaries = Face.InternalBoundaries(face)
|
363
|
+
offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
|
364
|
+
offset_internal_boundaries = []
|
365
|
+
for internal_boundary in internal_boundaries:
|
366
|
+
offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
|
367
|
+
return Face.ByWires(offset_external_boundary, offset_internal_boundaries, tolerance=tolerance)
|
368
|
+
|
369
|
+
@staticmethod
|
370
|
+
def ByShell(shell: topologic.Shell, origin: topologic.Vertex = None, angTolerance: float = 0.1, tolerance: float = 0.0001)-> topologic.Face:
|
371
|
+
"""
|
372
|
+
Creates a face by merging the faces of the input shell.
|
373
|
+
|
374
|
+
Parameters
|
375
|
+
----------
|
376
|
+
shell : topologic.Shell
|
377
|
+
The input shell.
|
378
|
+
angTolerance : float , optional
|
379
|
+
The desired angular tolerance. The default is 0.1.
|
380
|
+
tolerance : float , optional
|
381
|
+
The desired tolerance. The default is 0.0001.
|
382
|
+
|
383
|
+
Returns
|
384
|
+
-------
|
385
|
+
topologic.Face
|
386
|
+
The created face.
|
387
|
+
|
388
|
+
"""
|
389
|
+
from topologicpy.Vertex import Vertex
|
390
|
+
from topologicpy.Wire import Wire
|
391
|
+
from topologicpy.Shell import Shell
|
392
|
+
from topologicpy.Cluster import Cluster
|
393
|
+
from topologicpy.Topology import Topology
|
394
|
+
from topologicpy.Dictionary import Dictionary
|
395
|
+
from topologicpy.Helper import Helper
|
396
|
+
|
397
|
+
def planarizeList(wireList):
|
398
|
+
returnList = []
|
399
|
+
for aWire in wireList:
|
400
|
+
returnList.append(Wire.Planarize(aWire))
|
401
|
+
return returnList
|
402
|
+
|
403
|
+
if not isinstance(shell, topologic.Shell):
|
404
|
+
print("Face.ByShell - Error: The input shell parameter is not a valid toplogic shell. Returning None.")
|
405
|
+
return None
|
406
|
+
|
407
|
+
if origin == None:
|
408
|
+
origin = Topology.Centroid(shell)
|
409
|
+
if not isinstance(origin, topologic.Vertex):
|
410
|
+
print("Face.ByShell - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
|
411
|
+
return None
|
412
|
+
|
413
|
+
# Try the simple method first
|
414
|
+
face = None
|
415
|
+
ext_boundary = Wire.RemoveCollinearEdges(Shell.ExternalBoundary(shell))
|
416
|
+
if isinstance(ext_boundary, topologic.Wire):
|
417
|
+
face = Face.ByWire(ext_boundary)
|
418
|
+
elif isinstance(ext_boundary, topologic.Cluster):
|
419
|
+
wires = Topology.Wires(ext_boundary)
|
420
|
+
faces = [Face.ByWire(w) for w in wires]
|
421
|
+
areas = [Face.Area(f) for f in faces]
|
422
|
+
wires = Helper.Sort(wires, areas, reverseFlags=[True])
|
423
|
+
face = Face.ByWires(wires[0], wires[1:])
|
424
|
+
|
425
|
+
if isinstance(face, topologic.Face):
|
426
|
+
return face
|
427
|
+
world_origin = Vertex.Origin()
|
428
|
+
planar_shell = Shell.Planarize(shell)
|
429
|
+
normal = Face.Normal(Topology.Faces(planar_shell)[0])
|
430
|
+
planar_shell = Topology.Flatten(planar_shell, origin=origin, direction=normal)
|
431
|
+
vertices = Shell.Vertices(planar_shell)
|
432
|
+
new_vertices = []
|
433
|
+
for v in vertices:
|
434
|
+
x, y, z = Vertex.Coordinates(v)
|
435
|
+
new_v = Vertex.ByCoordinates(x,y,0)
|
436
|
+
new_vertices.append(new_v)
|
437
|
+
planar_shell = Topology.SelfMerge(Topology.ReplaceVertices(planar_shell, verticesA=vertices, verticesB=new_vertices), tolerance=tolerance)
|
438
|
+
ext_boundary = Shell.ExternalBoundary(planar_shell, tolerance=tolerance)
|
439
|
+
ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
|
440
|
+
if not isinstance(ext_boundary, topologic.Topology):
|
441
|
+
print("Face.ByShell - Error: Could not derive the external boundary of the input shell parameter. Returning None.")
|
442
|
+
return None
|
443
|
+
|
444
|
+
if isinstance(ext_boundary, topologic.Wire):
|
445
|
+
if not Topology.IsPlanar(ext_boundary, tolerance=tolerance):
|
446
|
+
ext_boundary = Wire.Planarize(ext_boundary, origin=origin, tolerance=tolerance)
|
447
|
+
ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
|
448
|
+
try:
|
449
|
+
face = Face.ByWire(ext_boundary)
|
450
|
+
face = Topology.Unflatten(face, origin=origin, direction=normal)
|
451
|
+
return face
|
452
|
+
except:
|
453
|
+
print("Face.ByShell - Error: The operation failed. Returning None.")
|
454
|
+
return None
|
455
|
+
elif isinstance(ext_boundary, topologic.Cluster): # The shell has holes.
|
456
|
+
wires = []
|
457
|
+
_ = ext_boundary.Wires(None, wires)
|
458
|
+
faces = []
|
459
|
+
areas = []
|
460
|
+
for wire in wires:
|
461
|
+
aFace = Face.ByWire(wire, tolerance=tolerance)
|
462
|
+
if not isinstance(aFace, topologic.Face):
|
463
|
+
print("Face.ByShell - Error: The operation failed. Returning None.")
|
464
|
+
return None
|
465
|
+
anArea = abs(Face.Area(aFace))
|
466
|
+
faces.append(aFace)
|
467
|
+
areas.append(anArea)
|
468
|
+
max_index = areas.index(max(areas))
|
469
|
+
ext_boundary = faces[max_index]
|
470
|
+
int_boundaries = list(set(faces) - set([ext_boundary]))
|
471
|
+
int_wires = []
|
472
|
+
for int_boundary in int_boundaries:
|
473
|
+
temp_wires = []
|
474
|
+
_ = int_boundary.Wires(None, temp_wires)
|
475
|
+
int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
|
476
|
+
temp_wires = []
|
477
|
+
_ = ext_boundary.Wires(None, temp_wires)
|
478
|
+
ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
|
479
|
+
face = Face.ByWires(ext_wire, int_wires)
|
480
|
+
|
481
|
+
face = Topology.Unflatten(face, origin=origin, direction=normal)
|
482
|
+
return face
|
483
|
+
else:
|
484
|
+
return None
|
485
|
+
|
486
|
+
@staticmethod
|
487
|
+
def ByVertices(vertices: list, tolerance: float = 0.0001) -> topologic.Face:
|
488
|
+
|
489
|
+
"""
|
490
|
+
Creates a face from the input list of vertices.
|
491
|
+
|
492
|
+
Parameters
|
493
|
+
----------
|
494
|
+
vertices : list
|
495
|
+
The input list of vertices.
|
496
|
+
tolerance : float , optional
|
497
|
+
The desired tolerance. The default is 0.0001.
|
498
|
+
|
499
|
+
Returns
|
500
|
+
-------
|
501
|
+
topologic.Face
|
502
|
+
The created face.
|
503
|
+
|
504
|
+
"""
|
505
|
+
from topologicpy.Topology import Topology
|
506
|
+
from topologicpy.Wire import Wire
|
507
|
+
|
508
|
+
if not isinstance(vertices, list):
|
509
|
+
return None
|
510
|
+
vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
511
|
+
if len(vertexList) < 3:
|
512
|
+
return None
|
513
|
+
|
514
|
+
w = Wire.ByVertices(vertexList, tolerance=tolerance)
|
515
|
+
f = Face.ByWire(w, tolerance=tolerance)
|
516
|
+
return f
|
517
|
+
|
518
|
+
@staticmethod
|
519
|
+
def ByVerticesCluster(cluster: topologic.Cluster, tolerance: float = 0.0001) -> topologic.Face:
|
520
|
+
"""
|
521
|
+
Creates a face from the input cluster of vertices.
|
522
|
+
|
523
|
+
Parameters
|
524
|
+
----------
|
525
|
+
cluster : topologic.Cluster
|
526
|
+
The input cluster of vertices.
|
527
|
+
tolerance : float , optional
|
528
|
+
The desired tolerance. The default is 0.0001.
|
529
|
+
|
530
|
+
Returns
|
531
|
+
-------
|
532
|
+
topologic.Face
|
533
|
+
The crearted face.
|
534
|
+
|
535
|
+
"""
|
536
|
+
from topologicpy.Cluster import Cluster
|
537
|
+
if not isinstance(cluster, topologic.Cluster):
|
538
|
+
return None
|
539
|
+
vertices = Cluster.Vertices(cluster)
|
540
|
+
return Face.ByVertices(vertices, tolerance=tolerance)
|
541
|
+
|
542
|
+
@staticmethod
|
543
|
+
def ByWire(wire: topologic.Wire, tolerance: float = 0.0001, silent=False) -> topologic.Face:
|
544
|
+
"""
|
545
|
+
Creates a face from the input closed wire.
|
546
|
+
|
547
|
+
Parameters
|
548
|
+
----------
|
549
|
+
wire : topologic.Wire
|
550
|
+
The input wire.
|
551
|
+
tolerance : float , optional
|
552
|
+
The desired tolerance. The default is 0.0001.
|
553
|
+
silent : bool , optional
|
554
|
+
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
|
555
|
+
|
556
|
+
Returns
|
557
|
+
-------
|
558
|
+
topologic.Face or list
|
559
|
+
The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.
|
560
|
+
|
561
|
+
"""
|
562
|
+
from topologicpy.Vertex import Vertex
|
563
|
+
from topologicpy.Wire import Wire
|
564
|
+
from topologicpy.Shell import Shell
|
565
|
+
from topologicpy.Cluster import Cluster
|
566
|
+
from topologicpy.Topology import Topology
|
567
|
+
from topologicpy.Dictionary import Dictionary
|
568
|
+
import random
|
569
|
+
|
570
|
+
def triangulateWire(wire):
|
571
|
+
wire = Topology.RemoveCollinearEdges(wire)
|
572
|
+
vertices = Topology.Vertices(wire)
|
573
|
+
shell = Shell.Delaunay(vertices)
|
574
|
+
if isinstance(shell, topologic.Topology):
|
575
|
+
return Topology.Faces(shell)
|
576
|
+
else:
|
577
|
+
return []
|
578
|
+
if not isinstance(wire, topologic.Wire):
|
579
|
+
if not silent:
|
580
|
+
print("Face.ByWire - Error: The input wire parameter is not a valid topologic wire. Returning None.")
|
581
|
+
return None
|
582
|
+
if not Wire.IsClosed(wire):
|
583
|
+
if not silent:
|
584
|
+
print("Face.ByWire - Error: The input wire parameter is not a closed topologic wire. Returning None.")
|
585
|
+
return None
|
586
|
+
|
587
|
+
edges = Wire.Edges(wire)
|
588
|
+
wire = Topology.SelfMerge(Cluster.ByTopologies(edges), tolerance=tolerance)
|
589
|
+
vertices = Topology.Vertices(wire)
|
590
|
+
fList = []
|
591
|
+
if isinstance(wire, topologic.Wire):
|
592
|
+
try:
|
593
|
+
fList = topologic.Face.ByExternalBoundary(wire)
|
594
|
+
except:
|
595
|
+
if not silent:
|
596
|
+
print("Face.ByWire - Warning: Could not create face by external boundary. Trying other methods.")
|
597
|
+
if len(vertices) > 3:
|
598
|
+
fList = triangulateWire(wire)
|
599
|
+
else:
|
600
|
+
fList = []
|
601
|
+
|
602
|
+
if not isinstance(fList, list):
|
603
|
+
fList = [fList]
|
604
|
+
|
605
|
+
returnList = []
|
606
|
+
for f in fList:
|
607
|
+
if Face.Area(f) < 0:
|
608
|
+
wire = Face.ExternalBoundary(f)
|
609
|
+
wire = Wire.Invert(wire)
|
610
|
+
try:
|
611
|
+
f = topologic.Face.ByExternalBoundary(wire)
|
612
|
+
returnList.append(f)
|
613
|
+
except:
|
614
|
+
pass
|
615
|
+
else:
|
616
|
+
returnList.append(f)
|
617
|
+
if len(returnList) == 0:
|
618
|
+
if not silent:
|
619
|
+
print("Face.ByWire - Error: Could not build a face from the input wire parameter. Returning None.")
|
620
|
+
return None
|
621
|
+
elif len(returnList) == 1:
|
622
|
+
return returnList[0]
|
623
|
+
else:
|
624
|
+
if not silent:
|
625
|
+
print("Face.ByWire - Warning: Could not build a single face from the input wire parameter. Returning a list of faces.")
|
626
|
+
return returnList
|
627
|
+
|
628
|
+
@staticmethod
|
629
|
+
def ByWires(externalBoundary: topologic.Wire, internalBoundaries: list = [], tolerance: float = 0.0001, silent: bool = False) -> topologic.Face:
|
630
|
+
"""
|
631
|
+
Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).
|
632
|
+
|
633
|
+
Parameters
|
634
|
+
----------
|
635
|
+
externalBoundary : topologic.Wire
|
636
|
+
The input external boundary.
|
637
|
+
internalBoundaries : list , optional
|
638
|
+
The input list of internal boundaries (closed wires). The default is an empty list.
|
639
|
+
tolerance : float , optional
|
640
|
+
The desired tolerance. The default is 0.0001.
|
641
|
+
silent : bool , optional
|
642
|
+
If set to False, error messages are printed. Otherwise, they are not. The default is False.
|
643
|
+
|
644
|
+
Returns
|
645
|
+
-------
|
646
|
+
topologic.Face
|
647
|
+
The created face.
|
648
|
+
|
649
|
+
"""
|
650
|
+
if not isinstance(externalBoundary, topologic.Wire):
|
651
|
+
if not silent:
|
652
|
+
print("Face.ByWires - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.")
|
653
|
+
return None
|
654
|
+
if not Wire.IsClosed(externalBoundary):
|
655
|
+
if not silent:
|
656
|
+
print("Face.ByWires - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.")
|
657
|
+
return None
|
658
|
+
ibList = [x for x in internalBoundaries if isinstance(x, topologic.Wire) and Wire.IsClosed(x)]
|
659
|
+
face = None
|
660
|
+
try:
|
661
|
+
face = topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList, tolerance)
|
662
|
+
except:
|
663
|
+
if not silent:
|
664
|
+
print("Face.ByWires - Error: The operation failed. Returning None.")
|
665
|
+
face = None
|
666
|
+
return face
|
667
|
+
|
668
|
+
|
669
|
+
@staticmethod
|
670
|
+
def ByWiresCluster(externalBoundary: topologic.Wire, internalBoundariesCluster: topologic.Cluster = None, tolerance: float = 0.0001, silent: bool = False) -> topologic.Face:
|
671
|
+
"""
|
672
|
+
Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).
|
673
|
+
|
674
|
+
Parameters
|
675
|
+
----------
|
676
|
+
externalBoundary : topologic.Wire
|
677
|
+
The input external boundary (closed wire).
|
678
|
+
internalBoundariesCluster : topologic.Cluster
|
679
|
+
The input cluster of internal boundaries (closed wires). The default is None.
|
680
|
+
tolerance : float , optional
|
681
|
+
The desired tolerance. The default is 0.0001.
|
682
|
+
silent : bool , optional
|
683
|
+
If set to False, error messages are printed. Otherwise, they are not. The default is False.
|
684
|
+
|
685
|
+
Returns
|
686
|
+
-------
|
687
|
+
topologic.Face
|
688
|
+
The created face.
|
689
|
+
|
690
|
+
"""
|
691
|
+
from topologicpy.Wire import Wire
|
692
|
+
from topologicpy.Cluster import Cluster
|
693
|
+
if not isinstance(externalBoundary, topologic.Wire):
|
694
|
+
if not silent:
|
695
|
+
print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.")
|
696
|
+
return None
|
697
|
+
if not Wire.IsClosed(externalBoundary):
|
698
|
+
if not silent:
|
699
|
+
print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.")
|
700
|
+
return None
|
701
|
+
if not internalBoundariesCluster:
|
702
|
+
internalBoundaries = []
|
703
|
+
elif not isinstance(internalBoundariesCluster, topologic.Cluster):
|
704
|
+
if not silent:
|
705
|
+
print("Face.ByWiresCluster - Error: The input internalBoundariesCluster parameter is not a valid topologic cluster. Returning None.")
|
706
|
+
return None
|
707
|
+
else:
|
708
|
+
internalBoundaries = Cluster.Wires(internalBoundariesCluster)
|
709
|
+
return Face.ByWires(externalBoundary, internalBoundaries, tolerance=tolerance, silent=silent)
|
710
|
+
|
711
|
+
@staticmethod
|
712
|
+
def NorthArrow(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0,
|
713
|
+
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
714
|
+
"""
|
715
|
+
Creates a north arrow.
|
716
|
+
|
717
|
+
Parameters
|
718
|
+
----------
|
719
|
+
origin : topologic.Vertex, optional
|
720
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
721
|
+
radius : float , optional
|
722
|
+
The radius of the circle. The default is 1.
|
723
|
+
sides : int , optional
|
724
|
+
The number of sides of the circle. The default is 16.
|
725
|
+
direction : list , optional
|
726
|
+
The vector representing the up direction of the circle. The default is [0, 0, 1].
|
727
|
+
northAngle : float , optional
|
728
|
+
The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
|
729
|
+
placement : str , optional
|
730
|
+
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".
|
731
|
+
tolerance : float , optional
|
732
|
+
The desired tolerance. The default is 0.0001.
|
733
|
+
|
734
|
+
Returns
|
735
|
+
-------
|
736
|
+
topologic.Face
|
737
|
+
The created circle.
|
738
|
+
|
739
|
+
"""
|
740
|
+
from topologicpy.Topology import Topology
|
741
|
+
from topologicpy.Vertex import Vertex
|
742
|
+
if not origin:
|
743
|
+
origin = Vertex.Origin()
|
744
|
+
|
745
|
+
c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0, 0, 1], placement="center", tolerance=tolerance)
|
746
|
+
r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft")
|
747
|
+
r = Topology.Translate(r, -0.005*radius,0,0)
|
748
|
+
arrow = Topology.Difference(c, r, tolerance=tolerance)
|
749
|
+
arrow = Topology.Rotate(arrow, origin=Vertex.Origin(), axis=[0, 0, 1], angle=northAngle)
|
750
|
+
if placement.lower() == "lowerleft":
|
751
|
+
arrow = Topology.Translate(arrow, radius, radius, 0)
|
752
|
+
elif placement.lower() == "upperleft":
|
753
|
+
arrow = Topology.Translate(arrow, radius, -radius, 0)
|
754
|
+
elif placement.lower() == "lowerright":
|
755
|
+
arrow = Topology.Translate(arrow, -radius, radius, 0)
|
756
|
+
elif placement.lower() == "upperright":
|
757
|
+
arrow = Topology.Translate(arrow, -radius, -radius, 0)
|
758
|
+
arrow = Topology.Place(arrow, originA=Vertex.Origin(), originB=origin)
|
759
|
+
arrow = Topology.Orient(arrow, orign=origin, direction=direction)
|
760
|
+
return arrow
|
761
|
+
|
762
|
+
@staticmethod
|
763
|
+
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],
|
764
|
+
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
765
|
+
"""
|
766
|
+
Creates a circle.
|
767
|
+
|
768
|
+
Parameters
|
769
|
+
----------
|
770
|
+
origin : topologic.Vertex, optional
|
771
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
772
|
+
radius : float , optional
|
773
|
+
The radius of the circle. The default is 1.
|
774
|
+
sides : int , optional
|
775
|
+
The number of sides of the circle. The default is 16.
|
776
|
+
fromAngle : float , optional
|
777
|
+
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
778
|
+
toAngle : float , optional
|
779
|
+
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
780
|
+
direction : list , optional
|
781
|
+
The vector representing the up direction of the circle. The default is [0, 0, 1].
|
782
|
+
placement : str , optional
|
783
|
+
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".
|
784
|
+
tolerance : float , optional
|
785
|
+
The desired tolerance. The default is 0.0001.
|
786
|
+
|
787
|
+
Returns
|
788
|
+
-------
|
789
|
+
topologic.Face
|
790
|
+
The created circle.
|
791
|
+
|
792
|
+
"""
|
793
|
+
from topologicpy.Wire import Wire
|
794
|
+
wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
|
795
|
+
if not isinstance(wire, topologic.Wire):
|
796
|
+
return None
|
797
|
+
return Face.ByWire(wire, tolerance=tolerance)
|
798
|
+
|
799
|
+
@staticmethod
|
800
|
+
def Compactness(face: topologic.Face, mantissa: int = 6) -> float:
|
801
|
+
"""
|
802
|
+
Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
803
|
+
|
804
|
+
Parameters
|
805
|
+
----------
|
806
|
+
face : topologic.Face
|
807
|
+
The input face.
|
808
|
+
mantissa : int , optional
|
809
|
+
The desired length of the mantissa. The default is 6.
|
810
|
+
|
811
|
+
Returns
|
812
|
+
-------
|
813
|
+
float
|
814
|
+
The compactness measure of the input face.
|
815
|
+
|
816
|
+
"""
|
817
|
+
exb = face.ExternalBoundary()
|
818
|
+
edges = []
|
819
|
+
_ = exb.Edges(None, edges)
|
820
|
+
perimeter = 0.0
|
821
|
+
for anEdge in edges:
|
822
|
+
perimeter = perimeter + abs(topologic.EdgeUtility.Length(anEdge))
|
823
|
+
area = abs(Face.Area(face))
|
824
|
+
compactness = 0
|
825
|
+
#From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
|
826
|
+
|
827
|
+
if area <= 0:
|
828
|
+
return None
|
829
|
+
if perimeter <= 0:
|
830
|
+
return None
|
831
|
+
compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
|
832
|
+
return round(compactness, mantissa)
|
833
|
+
|
834
|
+
@staticmethod
|
835
|
+
def CompassAngle(face: topologic.Face, north: list = None, mantissa: int = 6) -> float:
|
836
|
+
"""
|
837
|
+
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.
|
838
|
+
|
839
|
+
Parameters
|
840
|
+
----------
|
841
|
+
face : topologic.Face
|
842
|
+
The input face.
|
843
|
+
north : list , optional
|
844
|
+
The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
|
845
|
+
mantissa : int, optional
|
846
|
+
The length of the desired mantissa. The default is 6.
|
847
|
+
tolerance : float , optional
|
848
|
+
The desired tolerance. The default is 0.0001.
|
849
|
+
|
850
|
+
Returns
|
851
|
+
-------
|
852
|
+
float
|
853
|
+
The horizontal compass angle in degrees between the direction of the face and the second input vector.
|
854
|
+
|
855
|
+
"""
|
856
|
+
from topologicpy.Vector import Vector
|
857
|
+
if not isinstance(face, topologic.Face):
|
858
|
+
return None
|
859
|
+
if not north:
|
860
|
+
north = Vector.North()
|
861
|
+
dirA = Face.NormalAtParameters(face,mantissa=mantissa)
|
862
|
+
return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)
|
863
|
+
|
864
|
+
@staticmethod
|
865
|
+
def Edges(face: topologic.Face) -> list:
|
866
|
+
"""
|
867
|
+
Returns the edges of the input face.
|
868
|
+
|
869
|
+
Parameters
|
870
|
+
----------
|
871
|
+
face : topologic.Face
|
872
|
+
The input face.
|
873
|
+
|
874
|
+
Returns
|
875
|
+
-------
|
876
|
+
list
|
877
|
+
The list of edges.
|
878
|
+
|
879
|
+
"""
|
880
|
+
if not isinstance(face, topologic.Face):
|
881
|
+
return None
|
882
|
+
edges = []
|
883
|
+
_ = face.Edges(None, edges)
|
884
|
+
return edges
|
885
|
+
|
886
|
+
@staticmethod
|
887
|
+
def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0, 0, 1],
|
888
|
+
placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
889
|
+
"""
|
890
|
+
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
|
891
|
+
|
892
|
+
Parameters
|
893
|
+
----------
|
894
|
+
origin : topologic.Vertex , optional
|
895
|
+
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).
|
896
|
+
radius : float , optional
|
897
|
+
The radius of the hexagon determining the size of the tile. The default is 0.5.
|
898
|
+
direction : list , optional
|
899
|
+
The vector representing the up direction of the ellipse. The default is [0, 0, 1].
|
900
|
+
placement : str , optional
|
901
|
+
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".
|
902
|
+
tolerance : float , optional
|
903
|
+
The desired tolerance. The default is 0.0001.
|
904
|
+
|
905
|
+
Returns
|
906
|
+
--------
|
907
|
+
topologic.Face
|
908
|
+
The created Einstein tile.
|
909
|
+
|
910
|
+
"""
|
911
|
+
from topologicpy.Wire import Wire
|
912
|
+
|
913
|
+
wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
|
914
|
+
if not isinstance(wire, topologic.Wire):
|
915
|
+
print("Face.Einstein - Error: Could not create base wire for the Einstein tile. Returning None.")
|
916
|
+
return None
|
917
|
+
return Face.ByWire(wire, tolerance=tolerance)
|
918
|
+
|
919
|
+
@staticmethod
|
920
|
+
def ExteriorAngles(face: topologic.Face, includeInternalBoundaries=False, mantissa: int = 6) -> list:
|
921
|
+
"""
|
922
|
+
Returns the exterior angles of the input face in degrees. The face must be planar.
|
923
|
+
|
924
|
+
Parameters
|
925
|
+
----------
|
926
|
+
face : topologic.Face
|
927
|
+
The input face.
|
928
|
+
includeInternalBoundaries : bool , optional
|
929
|
+
If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list
|
930
|
+
of exterior angles of the external boundary and the second list will contain lists of the exterior angles of each of the
|
931
|
+
internal boundaries (holes). For example: [[270,270,270,270], [[270,270,270,270],[300,300,300]]]. If not, the returned list will be
|
932
|
+
a simple list of interior angles of the external boundary. For example: [270,270,270,270]. Please note that that the interior angles of the
|
933
|
+
internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
|
934
|
+
mantissa : int , optional
|
935
|
+
The desired length of the mantissa. The default is 6.
|
936
|
+
Returns
|
937
|
+
-------
|
938
|
+
list
|
939
|
+
The list of exterior angles.
|
940
|
+
"""
|
941
|
+
|
942
|
+
from topologicpy.Wire import Wire
|
943
|
+
|
944
|
+
if not isinstance(face, topologic.Face):
|
945
|
+
print("Face.ExteriorAngles - Error: The input face parameter is not a valid face. Returning None.")
|
946
|
+
return None
|
947
|
+
eb = Face.ExternalBoundary(face)
|
948
|
+
return_list = Wire.ExteriorAngles(eb, mantissa=mantissa)
|
949
|
+
if includeInternalBoundaries:
|
950
|
+
internal_boundaries = Face.InternalBoundaries(face)
|
951
|
+
ib_i_a_list = []
|
952
|
+
if len(internal_boundaries) > 0:
|
953
|
+
for ib in internal_boundaries:
|
954
|
+
ib_interior_angles = Wire.InteriorAngles(ib, mantissa=mantissa)
|
955
|
+
ib_i_a_list.append(ib_interior_angles)
|
956
|
+
if len(ib_i_a_list) > 0:
|
957
|
+
return_list = [return_list]+[ib_i_a_list]
|
958
|
+
return return_list
|
959
|
+
|
960
|
+
@staticmethod
|
961
|
+
def ExternalBoundary(face: topologic.Face) -> topologic.Wire:
|
962
|
+
"""
|
963
|
+
Returns the external boundary (closed wire) of the input face.
|
964
|
+
|
965
|
+
Parameters
|
966
|
+
----------
|
967
|
+
face : topologic.Face
|
968
|
+
The input face.
|
969
|
+
|
970
|
+
Returns
|
971
|
+
-------
|
972
|
+
topologic.Wire
|
973
|
+
The external boundary of the input face.
|
974
|
+
|
975
|
+
"""
|
976
|
+
return face.ExternalBoundary()
|
977
|
+
|
978
|
+
@staticmethod
|
979
|
+
def FacingToward(face: topologic.Face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
|
980
|
+
"""
|
981
|
+
Returns True if the input face is facing toward the input direction.
|
982
|
+
|
983
|
+
Parameters
|
984
|
+
----------
|
985
|
+
face : topologic.Face
|
986
|
+
The input face.
|
987
|
+
direction : list , optional
|
988
|
+
The input direction. The default is [0,0,-1].
|
989
|
+
asVertex : bool , optional
|
990
|
+
If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
|
991
|
+
tolerance : float , optional
|
992
|
+
The desired tolerance. The default is 0.0001.
|
993
|
+
|
994
|
+
Returns
|
995
|
+
-------
|
996
|
+
bool
|
997
|
+
True if the face is facing toward the direction. False otherwise.
|
998
|
+
|
999
|
+
"""
|
1000
|
+
faceNormal = topologic.FaceUtility.NormalAtParameters(face,0.5, 0.5)
|
1001
|
+
faceCenter = topologic.FaceUtility.VertexAtParameters(face,0.5,0.5)
|
1002
|
+
cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
|
1003
|
+
try:
|
1004
|
+
vList = [direction.X(), direction.Y(), direction.Z()]
|
1005
|
+
except:
|
1006
|
+
try:
|
1007
|
+
vList = [direction[0], direction[1], direction[2]]
|
1008
|
+
except:
|
1009
|
+
raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
|
1010
|
+
if asVertex:
|
1011
|
+
dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
|
1012
|
+
else:
|
1013
|
+
dV = vList
|
1014
|
+
uV = Vector.Normalize(dV)
|
1015
|
+
dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
|
1016
|
+
if dot < tolerance:
|
1017
|
+
return False
|
1018
|
+
return True
|
1019
|
+
|
1020
|
+
@staticmethod
|
1021
|
+
def Harmonize(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Face:
|
1022
|
+
"""
|
1023
|
+
Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.
|
1024
|
+
|
1025
|
+
Parameters
|
1026
|
+
----------
|
1027
|
+
face : topologic.Face
|
1028
|
+
The input face.
|
1029
|
+
tolerance : float , optional
|
1030
|
+
The desired tolerance. The default is 0.0001.
|
1031
|
+
|
1032
|
+
Returns
|
1033
|
+
-------
|
1034
|
+
topologic.Face
|
1035
|
+
The harmonized face.
|
1036
|
+
|
1037
|
+
"""
|
1038
|
+
from topologicpy.Vertex import Vertex
|
1039
|
+
from topologicpy.Wire import Wire
|
1040
|
+
from topologicpy.Topology import Topology
|
1041
|
+
from topologicpy.Dictionary import Dictionary
|
1042
|
+
|
1043
|
+
if not isinstance(face, topologic.Face):
|
1044
|
+
print("Face.Harmonize - Error: The input face parameter is not a valid face. Returning None.")
|
1045
|
+
return None
|
1046
|
+
normal = Face.Normal(face)
|
1047
|
+
origin = Topology.Centroid(face)
|
1048
|
+
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1049
|
+
world_origin = Vertex.Origin()
|
1050
|
+
vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
|
1051
|
+
harmonizedEB = Wire.ByVertices(vertices)
|
1052
|
+
internalBoundaries = Face.InternalBoundaries(flatFace)
|
1053
|
+
harmonizedIB = []
|
1054
|
+
for ib in internalBoundaries:
|
1055
|
+
ibVertices = Wire.Vertices(ib)
|
1056
|
+
harmonizedIB.append(Wire.ByVertices(ibVertices))
|
1057
|
+
harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB, tolerance=tolerance)
|
1058
|
+
harmonizedFace = Topology.Unflatten(harmonizedFace, origin=origin, direction=normal)
|
1059
|
+
return harmonizedFace
|
1060
|
+
|
1061
|
+
@staticmethod
|
1062
|
+
def InteriorAngles(face: topologic.Face, includeInternalBoundaries: bool = False, mantissa: int = 6) -> list:
|
1063
|
+
"""
|
1064
|
+
Returns the interior angles of the input face in degrees. The face must be planar.
|
1065
|
+
|
1066
|
+
Parameters
|
1067
|
+
----------
|
1068
|
+
face : topologic.Face
|
1069
|
+
The input face.
|
1070
|
+
includeInternalBoundaries : bool , optional
|
1071
|
+
If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list
|
1072
|
+
of interior angles of the external boundary and the second list will contain lists of the interior angles of each of the
|
1073
|
+
internal boundaries (holes). For example: [[90,90,90,90], [[90,90,90,90],[60,60,60]]]. If not, the returned list will be
|
1074
|
+
a simple list of interior angles of the external boundary. For example: [90,90,90,90]. Please note that that the interior angles of the
|
1075
|
+
internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
|
1076
|
+
mantissa : int , optional
|
1077
|
+
The desired length of the mantissa. The default is 6.
|
1078
|
+
Returns
|
1079
|
+
-------
|
1080
|
+
list
|
1081
|
+
The list of interior angles.
|
1082
|
+
"""
|
1083
|
+
|
1084
|
+
from topologicpy.Wire import Wire
|
1085
|
+
|
1086
|
+
if not isinstance(face, topologic.Face):
|
1087
|
+
print("Face.InteriorAngles - Error: The input face parameter is not a valid face. Returning None.")
|
1088
|
+
return None
|
1089
|
+
eb = Face.ExternalBoundary(face)
|
1090
|
+
return_list = Wire.InteriorAngles(eb, mantissa=mantissa)
|
1091
|
+
if includeInternalBoundaries:
|
1092
|
+
internal_boundaries = Face.InternalBoundaries(face)
|
1093
|
+
ib_i_a_list = []
|
1094
|
+
if len(internal_boundaries) > 0:
|
1095
|
+
for ib in internal_boundaries:
|
1096
|
+
ib_interior_angles = Wire.ExteriorAngles(ib, mantissa=mantissa)
|
1097
|
+
ib_i_a_list.append(ib_interior_angles)
|
1098
|
+
if len(ib_i_a_list) > 0:
|
1099
|
+
return_list = [return_list]+[ib_i_a_list]
|
1100
|
+
return return_list
|
1101
|
+
|
1102
|
+
@staticmethod
|
1103
|
+
def InternalBoundaries(face: topologic.Face) -> list:
|
1104
|
+
"""
|
1105
|
+
Returns the internal boundaries (closed wires) of the input face.
|
1106
|
+
|
1107
|
+
Parameters
|
1108
|
+
----------
|
1109
|
+
face : topologic.Face
|
1110
|
+
The input face.
|
1111
|
+
|
1112
|
+
Returns
|
1113
|
+
-------
|
1114
|
+
list
|
1115
|
+
The list of internal boundaries (closed wires).
|
1116
|
+
|
1117
|
+
"""
|
1118
|
+
if not isinstance(face, topologic.Face):
|
1119
|
+
return None
|
1120
|
+
wires = []
|
1121
|
+
_ = face.InternalBoundaries(wires)
|
1122
|
+
return list(wires)
|
1123
|
+
|
1124
|
+
@staticmethod
|
1125
|
+
def InternalVertex(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Vertex:
|
1126
|
+
"""
|
1127
|
+
Creates a vertex guaranteed to be inside the input face.
|
1128
|
+
|
1129
|
+
Parameters
|
1130
|
+
----------
|
1131
|
+
face : topologic.Face
|
1132
|
+
The input face.
|
1133
|
+
tolerance : float , optional
|
1134
|
+
The desired tolerance. The default is 0.0001.
|
1135
|
+
|
1136
|
+
Returns
|
1137
|
+
-------
|
1138
|
+
topologic.Vertex
|
1139
|
+
The created vertex.
|
1140
|
+
|
1141
|
+
"""
|
1142
|
+
from topologicpy.Vertex import Vertex
|
1143
|
+
from topologicpy.Topology import Topology
|
1144
|
+
if not isinstance(face, topologic.Face):
|
1145
|
+
return None
|
1146
|
+
v = Topology.Centroid(face)
|
1147
|
+
if Vertex.IsInternal(v, face, tolerance=tolerance):
|
1148
|
+
return v
|
1149
|
+
l = [0.4,0.6,0.3,0.7,0.2,0.8,0.1,0.9]
|
1150
|
+
for u in l:
|
1151
|
+
for v in l:
|
1152
|
+
v = Face.VertexByParameters(face, u, v)
|
1153
|
+
if Vertex.IsInternal(v, face, tolerance=tolerance):
|
1154
|
+
return v
|
1155
|
+
v = topologic.FaceUtility.InternalVertex(face, tolerance)
|
1156
|
+
return v
|
1157
|
+
|
1158
|
+
@staticmethod
|
1159
|
+
def Invert(face: topologic.Face, tolerance: float = 0.0001) -> topologic.Face:
|
1160
|
+
"""
|
1161
|
+
Creates a face that is an inverse (mirror) of the input face.
|
1162
|
+
|
1163
|
+
Parameters
|
1164
|
+
----------
|
1165
|
+
face : topologic.Face
|
1166
|
+
The input face.
|
1167
|
+
tolerance : float , optional
|
1168
|
+
The desired tolerance. The default is 0.0001.
|
1169
|
+
|
1170
|
+
Returns
|
1171
|
+
-------
|
1172
|
+
topologic.Face
|
1173
|
+
The inverted face.
|
1174
|
+
|
1175
|
+
"""
|
1176
|
+
from topologicpy.Wire import Wire
|
1177
|
+
|
1178
|
+
if not isinstance(face, topologic.Face):
|
1179
|
+
return None
|
1180
|
+
eb = Face.ExternalBoundary(face)
|
1181
|
+
vertices = Wire.Vertices(eb)
|
1182
|
+
vertices.reverse()
|
1183
|
+
inverted_wire = Wire.ByVertices(vertices)
|
1184
|
+
internal_boundaries = Face.InternalBoundaries(face)
|
1185
|
+
if not internal_boundaries:
|
1186
|
+
inverted_face = Face.ByWire(inverted_wire, tolerance=tolerance)
|
1187
|
+
else:
|
1188
|
+
inverted_face = Face.ByWires(inverted_wire, internal_boundaries, tolerance=tolerance)
|
1189
|
+
return inverted_face
|
1190
|
+
|
1191
|
+
@staticmethod
|
1192
|
+
def IsCoplanar(faceA: topologic.Face, faceB: topologic.Face, tolerance: float = 0.0001) -> bool:
|
1193
|
+
"""
|
1194
|
+
Returns True if the two input faces are coplanar. Returns False otherwise.
|
1195
|
+
|
1196
|
+
Parameters
|
1197
|
+
----------
|
1198
|
+
faceA : topologic.Face
|
1199
|
+
The first input face.
|
1200
|
+
faceB : topologic.Face
|
1201
|
+
The second input face
|
1202
|
+
tolerance : float , optional
|
1203
|
+
The desired tolerance. The deafault is 0.0001.
|
1204
|
+
|
1205
|
+
Raises
|
1206
|
+
------
|
1207
|
+
Exception
|
1208
|
+
Raises an exception if the angle between the two input faces cannot be determined.
|
1209
|
+
|
1210
|
+
Returns
|
1211
|
+
-------
|
1212
|
+
bool
|
1213
|
+
True if the two input faces are coplanar. False otherwise.
|
1214
|
+
|
1215
|
+
"""
|
1216
|
+
if not isinstance(faceA, topologic.Face):
|
1217
|
+
print("Face.IsInide - Error: The input faceA parameter is not a valid topologic face. Returning None.")
|
1218
|
+
return None
|
1219
|
+
if not isinstance(faceB, topologic.Face):
|
1220
|
+
print("Face.IsInide - Error: The input faceB parameter is not a valid topologic face. Returning None.")
|
1221
|
+
return None
|
1222
|
+
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
|
1223
|
+
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
|
1224
|
+
return Vector.IsCollinear(dirA, dirB)
|
1225
|
+
|
1226
|
+
@staticmethod
|
1227
|
+
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:
|
1228
|
+
"""
|
1229
|
+
Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.
|
1230
|
+
|
1231
|
+
Parameters
|
1232
|
+
----------
|
1233
|
+
face : topologic.Face
|
1234
|
+
The input face.
|
1235
|
+
resolution : int , optional
|
1236
|
+
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.
|
1237
|
+
externalVertices : bool , optional
|
1238
|
+
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.
|
1239
|
+
internalVertices : bool , optional
|
1240
|
+
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.
|
1241
|
+
toLeavesOnly : bool , optional
|
1242
|
+
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.
|
1243
|
+
angTolerance : float , optional
|
1244
|
+
The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
|
1245
|
+
tolerance : float , optional
|
1246
|
+
The desired tolerance. The default is 0.0001.
|
1247
|
+
|
1248
|
+
Returns
|
1249
|
+
-------
|
1250
|
+
topologic.Wire
|
1251
|
+
The medial axis of the input face.
|
1252
|
+
|
1253
|
+
"""
|
1254
|
+
from topologicpy.Vertex import Vertex
|
1255
|
+
from topologicpy.Edge import Edge
|
1256
|
+
from topologicpy.Wire import Wire
|
1257
|
+
from topologicpy.Shell import Shell
|
1258
|
+
from topologicpy.Cluster import Cluster
|
1259
|
+
from topologicpy.Topology import Topology
|
1260
|
+
from topologicpy.Dictionary import Dictionary
|
1261
|
+
|
1262
|
+
def touchesEdge(vertex,edges, tolerance=0.0001):
|
1263
|
+
if not isinstance(vertex, topologic.Vertex):
|
1264
|
+
return False
|
1265
|
+
for edge in edges:
|
1266
|
+
u = Edge.ParameterAtVertex(edge, vertex, mantissa=6)
|
1267
|
+
if not u:
|
1268
|
+
continue
|
1269
|
+
if 0<u<1:
|
1270
|
+
return True
|
1271
|
+
return False
|
1272
|
+
|
1273
|
+
# Flatten the input face
|
1274
|
+
origin = Topology.Centroid(face)
|
1275
|
+
normal = Face.Normal(face)
|
1276
|
+
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1277
|
+
|
1278
|
+
# Create a Vertex at the world's origin (0, 0, 0)
|
1279
|
+
world_origin = Vertex.Origin()
|
1280
|
+
|
1281
|
+
faceEdges = Face.Edges(flatFace)
|
1282
|
+
vertices = []
|
1283
|
+
resolution = 10 - resolution
|
1284
|
+
resolution = min(max(resolution, 1), 10)
|
1285
|
+
for e in faceEdges:
|
1286
|
+
for n in range(resolution, 100, resolution):
|
1287
|
+
vertices.append(Edge.VertexByParameter(e,n*0.01))
|
1288
|
+
|
1289
|
+
voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
|
1290
|
+
voronoiEdges = Shell.Edges(voronoi)
|
1291
|
+
|
1292
|
+
medialAxisEdges = []
|
1293
|
+
for e in voronoiEdges:
|
1294
|
+
sv = Edge.StartVertex(e)
|
1295
|
+
ev = Edge.EndVertex(e)
|
1296
|
+
svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
|
1297
|
+
evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
|
1298
|
+
if not svTouchesEdge and not evTouchesEdge:
|
1299
|
+
medialAxisEdges.append(e)
|
1300
|
+
|
1301
|
+
extBoundary = Face.ExternalBoundary(flatFace)
|
1302
|
+
extVertices = Wire.Vertices(extBoundary)
|
1303
|
+
|
1304
|
+
intBoundaries = Face.InternalBoundaries(flatFace)
|
1305
|
+
intVertices = []
|
1306
|
+
for ib in intBoundaries:
|
1307
|
+
intVertices = intVertices+Wire.Vertices(ib)
|
1308
|
+
|
1309
|
+
theVertices = []
|
1310
|
+
if internalVertices:
|
1311
|
+
theVertices = theVertices+intVertices
|
1312
|
+
if externalVertices:
|
1313
|
+
theVertices = theVertices+extVertices
|
1314
|
+
|
1315
|
+
tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance)
|
1316
|
+
if isinstance(tempWire, topologic.Wire) and angTolerance > 0:
|
1317
|
+
tempWire = Wire.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
|
1318
|
+
medialAxisEdges = Wire.Edges(tempWire)
|
1319
|
+
for v in theVertices:
|
1320
|
+
nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)
|
1321
|
+
|
1322
|
+
if isinstance(nv, topologic.Vertex):
|
1323
|
+
if toLeavesOnly:
|
1324
|
+
adjVertices = Topology.AdjacentTopologies(nv, tempWire)
|
1325
|
+
if len(adjVertices) < 2:
|
1326
|
+
medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance))
|
1327
|
+
else:
|
1328
|
+
medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance))
|
1329
|
+
medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance)
|
1330
|
+
if isinstance(medialAxis, topologic.Wire) and angTolerance > 0:
|
1331
|
+
medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
|
1332
|
+
medialAxis = Topology.Unflatten(medialAxis, origin=origin,direction=normal)
|
1333
|
+
return medialAxis
|
1334
|
+
|
1335
|
+
@staticmethod
|
1336
|
+
def Normal(face: topologic.Face, outputType: str = "xyz", mantissa: int = 6) -> list:
|
1337
|
+
"""
|
1338
|
+
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
|
1339
|
+
|
1340
|
+
Parameters
|
1341
|
+
----------
|
1342
|
+
face : topologic.Face
|
1343
|
+
The input face.
|
1344
|
+
outputType : string , optional
|
1345
|
+
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
|
1346
|
+
mantissa : int , optional
|
1347
|
+
The desired length of the mantissa. The default is 6.
|
1348
|
+
|
1349
|
+
Returns
|
1350
|
+
-------
|
1351
|
+
list
|
1352
|
+
The normal vector to the input face. This is computed at the approximate center of the face.
|
1353
|
+
|
1354
|
+
"""
|
1355
|
+
return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
|
1356
|
+
|
1357
|
+
@staticmethod
|
1358
|
+
def NormalAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 6) -> list:
|
1359
|
+
"""
|
1360
|
+
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
|
1361
|
+
|
1362
|
+
Parameters
|
1363
|
+
----------
|
1364
|
+
face : topologic.Face
|
1365
|
+
The input face.
|
1366
|
+
u : float , optional
|
1367
|
+
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
|
1368
|
+
v : float , optional
|
1369
|
+
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
|
1370
|
+
outputType : string , optional
|
1371
|
+
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
|
1372
|
+
mantissa : int , optional
|
1373
|
+
The desired length of the mantissa. The default is 6.
|
1374
|
+
|
1375
|
+
Returns
|
1376
|
+
-------
|
1377
|
+
list
|
1378
|
+
The normal vector to the input face.
|
1379
|
+
|
1380
|
+
"""
|
1381
|
+
returnResult = []
|
1382
|
+
try:
|
1383
|
+
coords = topologic.FaceUtility.NormalAtParameters(face, u, v)
|
1384
|
+
x = round(coords[0], mantissa)
|
1385
|
+
y = round(coords[1], mantissa)
|
1386
|
+
z = round(coords[2], mantissa)
|
1387
|
+
outputType = list(outputType.lower())
|
1388
|
+
for axis in outputType:
|
1389
|
+
if axis == "x":
|
1390
|
+
returnResult.append(x)
|
1391
|
+
elif axis == "y":
|
1392
|
+
returnResult.append(y)
|
1393
|
+
elif axis == "z":
|
1394
|
+
returnResult.append(z)
|
1395
|
+
except:
|
1396
|
+
returnResult = None
|
1397
|
+
return returnResult
|
1398
|
+
|
1399
|
+
@staticmethod
|
1400
|
+
def NormalEdge(face: topologic.Face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False) -> topologic.Edge:
|
1401
|
+
"""
|
1402
|
+
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.
|
1403
|
+
|
1404
|
+
Parameters
|
1405
|
+
----------
|
1406
|
+
face : topologic.Face
|
1407
|
+
The input face.
|
1408
|
+
length : float , optional
|
1409
|
+
The desired length of the normal edge. The default is 1.
|
1410
|
+
tolerance : float , optional
|
1411
|
+
The desired tolerance. The default is 0.0001.
|
1412
|
+
|
1413
|
+
Returns
|
1414
|
+
-------
|
1415
|
+
topologic.Edge
|
1416
|
+
The created normal edge to the input face. This is computed at the approximate center of the face.
|
1417
|
+
|
1418
|
+
"""
|
1419
|
+
from topologicpy.Edge import Edge
|
1420
|
+
|
1421
|
+
if not isinstance(face, topologic.Face):
|
1422
|
+
if not silent:
|
1423
|
+
print("Face.NormalEdge - Error: The input face parameter is not a valid face. Retuning None.")
|
1424
|
+
return None
|
1425
|
+
if length < tolerance:
|
1426
|
+
if not silent:
|
1427
|
+
print("Face.NormalEdge - Error: The input length parameter is less than the input tolerance. Retuning None.")
|
1428
|
+
return None
|
1429
|
+
iv = Face.InternalVertex(face)
|
1430
|
+
u, v = Face.VertexParameters(face, iv)
|
1431
|
+
vec = Face.NormalAtParameters(face, u=u, v=v)
|
1432
|
+
ev = Topology.TranslateByDirectionDistance(iv, vec, length)
|
1433
|
+
return Edge.ByVertices([iv, ev], tolerance=tolerance, silent=silent)
|
1434
|
+
|
1435
|
+
@staticmethod
|
1436
|
+
def NormalEdgeAtParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001) -> topologic.Edge:
|
1437
|
+
"""
|
1438
|
+
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.
|
1439
|
+
|
1440
|
+
Parameters
|
1441
|
+
----------
|
1442
|
+
face : topologic.Face
|
1443
|
+
The input face.
|
1444
|
+
u : float , optional
|
1445
|
+
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
|
1446
|
+
v : float , optional
|
1447
|
+
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
|
1448
|
+
length : float , optional
|
1449
|
+
The desired length of the normal edge. The default is 1.
|
1450
|
+
tolerance : float , optional
|
1451
|
+
The desired tolerance. The default is 0.0001.
|
1452
|
+
|
1453
|
+
Returns
|
1454
|
+
-------
|
1455
|
+
topologic.Edge
|
1456
|
+
The created normal edge to the input face. This is computed at the approximate center of the face.
|
1457
|
+
|
1458
|
+
"""
|
1459
|
+
from topologicpy.Edge import Edge
|
1460
|
+
from topologicpy.Topology import Topology
|
1461
|
+
if not isinstance(face, topologic.Face):
|
1462
|
+
return None
|
1463
|
+
sv = Face.VertexByParameters(face=face, u=u, v=v)
|
1464
|
+
vec = Face.NormalAtParameters(face, u=u, v=v)
|
1465
|
+
ev = Topology.TranslateByDirectionDistance(sv, vec, length)
|
1466
|
+
return Edge.ByVertices([sv, ev], tolerance=tolerance, silent=True)
|
1467
|
+
|
1468
|
+
@staticmethod
|
1469
|
+
def PlaneEquation(face: topologic.Face, mantissa: int = 6) -> dict:
|
1470
|
+
"""
|
1471
|
+
Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
|
1472
|
+
|
1473
|
+
Parameters
|
1474
|
+
----------
|
1475
|
+
face : topologic.Face
|
1476
|
+
The input face.
|
1477
|
+
|
1478
|
+
Returns
|
1479
|
+
-------
|
1480
|
+
dict
|
1481
|
+
The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].
|
1482
|
+
|
1483
|
+
"""
|
1484
|
+
from topologicpy.Topology import Topology
|
1485
|
+
from topologicpy.Vertex import Vertex
|
1486
|
+
import random
|
1487
|
+
import time
|
1488
|
+
|
1489
|
+
if not isinstance(face, topologic.Face):
|
1490
|
+
print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.")
|
1491
|
+
return None
|
1492
|
+
vertices = Topology.Vertices(face)
|
1493
|
+
if len(vertices) < 3:
|
1494
|
+
print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.")
|
1495
|
+
return None
|
1496
|
+
return Vertex.PlaneEquation(vertices, mantissa=mantissa)
|
1497
|
+
|
1498
|
+
@staticmethod
|
1499
|
+
def Planarize(face: topologic.Face, origin: topologic.Vertex = None,
|
1500
|
+
tolerance: float = 0.0001) -> topologic.Face:
|
1501
|
+
"""
|
1502
|
+
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.
|
1503
|
+
|
1504
|
+
Parameters
|
1505
|
+
----------
|
1506
|
+
face : topologic.Face
|
1507
|
+
The input face.
|
1508
|
+
origin : topologic.Vertex , optional
|
1509
|
+
The desired vertex to use as the origin of the plane to project the face unto. If set to None, the centroidof the input face is used. The default is None.
|
1510
|
+
tolerance : float , optional
|
1511
|
+
The desired tolerance. The default is 0.0001.
|
1512
|
+
|
1513
|
+
Returns
|
1514
|
+
-------
|
1515
|
+
topologic.Face
|
1516
|
+
The planarized face.
|
1517
|
+
|
1518
|
+
"""
|
1519
|
+
|
1520
|
+
from topologicpy.Wire import Wire
|
1521
|
+
from topologicpy.Topology import Topology
|
1522
|
+
|
1523
|
+
if not isinstance(face, topologic.Face):
|
1524
|
+
return None
|
1525
|
+
if not isinstance(origin, topologic.Vertex):
|
1526
|
+
origin = Topology.Centroid(face)
|
1527
|
+
eb = Face.ExternalBoundary(face)
|
1528
|
+
plan_eb = Wire.Planarize(eb, origin=origin)
|
1529
|
+
ib_list = Face.InternalBoundaries(face)
|
1530
|
+
plan_ib_list = []
|
1531
|
+
for ib in ib_list:
|
1532
|
+
plan_ib_list.append(Wire.Planarize(ib, origin=origin))
|
1533
|
+
plan_face = Face.ByWires(plan_eb, plan_ib_list)
|
1534
|
+
return plan_face
|
1535
|
+
|
1536
|
+
@staticmethod
|
1537
|
+
def Project(faceA: topologic.Face, faceB: topologic.Face, direction : list = None,
|
1538
|
+
mantissa: int = 6, tolerance: float = 0.0001) -> topologic.Face:
|
1539
|
+
"""
|
1540
|
+
Creates a projection of the first input face unto the second input face.
|
1541
|
+
|
1542
|
+
Parameters
|
1543
|
+
----------
|
1544
|
+
faceA : topologic.Face
|
1545
|
+
The face to be projected.
|
1546
|
+
faceB : topologic.Face
|
1547
|
+
The face unto which the first input face will be projected.
|
1548
|
+
direction : list, optional
|
1549
|
+
The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
|
1550
|
+
mantissa : int , optional
|
1551
|
+
The desired length of the mantissa. The default is 6.
|
1552
|
+
tolerance : float , optional
|
1553
|
+
The desired tolerance. The default is 0.0001.
|
1554
|
+
|
1555
|
+
Returns
|
1556
|
+
-------
|
1557
|
+
topologic.Face
|
1558
|
+
The projected Face.
|
1559
|
+
|
1560
|
+
"""
|
1561
|
+
|
1562
|
+
from topologicpy.Wire import Wire
|
1563
|
+
|
1564
|
+
if not faceA:
|
1565
|
+
return None
|
1566
|
+
if not isinstance(faceA, topologic.Face):
|
1567
|
+
return None
|
1568
|
+
if not faceB:
|
1569
|
+
return None
|
1570
|
+
if not isinstance(faceB, topologic.Face):
|
1571
|
+
return None
|
1572
|
+
|
1573
|
+
eb = faceA.ExternalBoundary()
|
1574
|
+
ib_list = []
|
1575
|
+
_ = faceA.InternalBoundaries(ib_list)
|
1576
|
+
p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance)
|
1577
|
+
p_ib_list = []
|
1578
|
+
for ib in ib_list:
|
1579
|
+
temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance)
|
1580
|
+
if temp_ib:
|
1581
|
+
p_ib_list.append(temp_ib)
|
1582
|
+
return Face.ByWires(p_eb, p_ib_list, tolerance=tolerance)
|
1583
|
+
|
1584
|
+
@staticmethod
|
1585
|
+
def RectangleByPlaneEquation(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, placement: str = "center", equation: dict = None, tolerance: float = 0.0001) -> topologic.Face:
|
1586
|
+
from topologicpy.Vertex import Vertex
|
1587
|
+
# Extract coefficients of the plane equation
|
1588
|
+
a = equation['a']
|
1589
|
+
b = equation['b']
|
1590
|
+
c = equation['c']
|
1591
|
+
d = equation['d']
|
1592
|
+
|
1593
|
+
# Calculate the normal vector of the plane
|
1594
|
+
direction = np.array([a, b, c], dtype=float)
|
1595
|
+
direction /= np.linalg.norm(direction)
|
1596
|
+
direction = [x for x in direction]
|
1597
|
+
|
1598
|
+
return Face.Rectangle(origin=origin, width=width, length=length, direction = direction, placement=placement, tolerance=tolerance)
|
1599
|
+
|
1600
|
+
@staticmethod
|
1601
|
+
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:
|
1602
|
+
"""
|
1603
|
+
Creates a rectangle.
|
1604
|
+
|
1605
|
+
Parameters
|
1606
|
+
----------
|
1607
|
+
origin : topologic.Vertex, optional
|
1608
|
+
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0).
|
1609
|
+
width : float , optional
|
1610
|
+
The width of the rectangle. The default is 1.0.
|
1611
|
+
length : float , optional
|
1612
|
+
The length of the rectangle. The default is 1.0.
|
1613
|
+
direction : list , optional
|
1614
|
+
The vector representing the up direction of the rectangle. The default is [0, 0, 1].
|
1615
|
+
placement : str , optional
|
1616
|
+
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".
|
1617
|
+
tolerance : float , optional
|
1618
|
+
The desired tolerance. The default is 0.0001.
|
1619
|
+
|
1620
|
+
Returns
|
1621
|
+
-------
|
1622
|
+
topologic.Face
|
1623
|
+
The created face.
|
1624
|
+
|
1625
|
+
"""
|
1626
|
+
from topologicpy.Wire import Wire
|
1627
|
+
|
1628
|
+
wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
|
1629
|
+
if not isinstance(wire, topologic.Wire):
|
1630
|
+
print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.")
|
1631
|
+
return None
|
1632
|
+
return Face.ByWire(wire, tolerance=tolerance)
|
1633
|
+
|
1634
|
+
@staticmethod
|
1635
|
+
def RemoveCollinearEdges(face: topologic.Face, angTolerance: float = 0.1, tolerance: float = 0.0001) -> topologic.Wire:
|
1636
|
+
"""
|
1637
|
+
Removes any collinear edges in the input face.
|
1638
|
+
|
1639
|
+
Parameters
|
1640
|
+
----------
|
1641
|
+
face : topologic.Face
|
1642
|
+
The input face.
|
1643
|
+
angTolerance : float , optional
|
1644
|
+
The desired angular tolerance. The default is 0.1.
|
1645
|
+
tolerance : float , optional
|
1646
|
+
The desired tolerance. The default is 0.0001.
|
1647
|
+
|
1648
|
+
Returns
|
1649
|
+
-------
|
1650
|
+
topologic.Face
|
1651
|
+
The created face without any collinear edges.
|
1652
|
+
|
1653
|
+
"""
|
1654
|
+
from topologicpy.Wire import Wire
|
1655
|
+
|
1656
|
+
if not isinstance(face, topologic.Face):
|
1657
|
+
print("Face.RemoveCollinearEdges - Error: The input face parameter is not a valid face. Returning None.")
|
1658
|
+
return None
|
1659
|
+
eb = Wire.RemoveCollinearEdges(Face.Wire(face), angTolerance=angTolerance, tolerance=tolerance)
|
1660
|
+
ib = [Wire.RemoveCollinearEdges(w, angTolerance=angTolerance, tolerance=tolerance) for w in Face.InternalBoundaries(face)]
|
1661
|
+
return Face.ByWires(eb, ib)
|
1662
|
+
|
1663
|
+
@staticmethod
|
1664
|
+
def Skeleton(face, tolerance=0.001):
|
1665
|
+
"""
|
1666
|
+
Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1667
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1668
|
+
|
1669
|
+
Parameters
|
1670
|
+
----------
|
1671
|
+
face : topologic.Face
|
1672
|
+
The input face.
|
1673
|
+
tolerance : float , optional
|
1674
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better)
|
1675
|
+
|
1676
|
+
Returns
|
1677
|
+
-------
|
1678
|
+
topologic.Wire
|
1679
|
+
The created straight skeleton.
|
1680
|
+
|
1681
|
+
"""
|
1682
|
+
from topologicpy.Wire import Wire
|
1683
|
+
if not isinstance(face, topologic.Face):
|
1684
|
+
print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
|
1685
|
+
return None
|
1686
|
+
return Wire.Skeleton(face, tolerance=tolerance)
|
1687
|
+
|
1688
|
+
@staticmethod
|
1689
|
+
def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Face:
|
1690
|
+
"""
|
1691
|
+
Creates a square.
|
1692
|
+
|
1693
|
+
Parameters
|
1694
|
+
----------
|
1695
|
+
origin : topologic.Vertex , optional
|
1696
|
+
The location of the origin of the square. The default is None which results in the square being placed at (0, 0, 0).
|
1697
|
+
size : float , optional
|
1698
|
+
The size of the square. The default is 1.0.
|
1699
|
+
direction : list , optional
|
1700
|
+
The vector representing the up direction of the square. The default is [0, 0, 1].
|
1701
|
+
placement : str , optional
|
1702
|
+
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".
|
1703
|
+
tolerance : float , optional
|
1704
|
+
The desired tolerance. The default is 0.0001.
|
1705
|
+
|
1706
|
+
Returns
|
1707
|
+
-------
|
1708
|
+
topologic.Face
|
1709
|
+
The created square.
|
1710
|
+
|
1711
|
+
"""
|
1712
|
+
return Face.Rectangle(origin=origin, width=size, length=size, direction=direction, placement=placement, tolerance=tolerance)
|
1713
|
+
|
1714
|
+
@staticmethod
|
1715
|
+
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:
|
1716
|
+
"""
|
1717
|
+
Creates a star.
|
1718
|
+
|
1719
|
+
Parameters
|
1720
|
+
----------
|
1721
|
+
origin : topologic.Vertex, optional
|
1722
|
+
The location of the origin of the star. The default is None which results in the star being placed at (0, 0, 0).
|
1723
|
+
radiusA : float , optional
|
1724
|
+
The outer radius of the star. The default is 1.0.
|
1725
|
+
radiusB : float , optional
|
1726
|
+
The outer radius of the star. The default is 0.4.
|
1727
|
+
rays : int , optional
|
1728
|
+
The number of star rays. The default is 5.
|
1729
|
+
direction : list , optional
|
1730
|
+
The vector representing the up direction of the star. The default is [0, 0, 1].
|
1731
|
+
placement : str , optional
|
1732
|
+
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".
|
1733
|
+
tolerance : float , optional
|
1734
|
+
The desired tolerance. The default is 0.0001.
|
1735
|
+
|
1736
|
+
Returns
|
1737
|
+
-------
|
1738
|
+
topologic.Face
|
1739
|
+
The created face.
|
1740
|
+
|
1741
|
+
"""
|
1742
|
+
from topologicpy.Wire import Wire
|
1743
|
+
wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
|
1744
|
+
if not isinstance(wire, topologic.Wire):
|
1745
|
+
print("Face.Rectangle - Error: Could not create the base wire for the star. Returning None.")
|
1746
|
+
return None
|
1747
|
+
return Face.ByWire(wire, tolerance=tolerance)
|
1748
|
+
|
1749
|
+
@staticmethod
|
1750
|
+
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:
|
1751
|
+
"""
|
1752
|
+
Creates a trapezoid.
|
1753
|
+
|
1754
|
+
Parameters
|
1755
|
+
----------
|
1756
|
+
origin : topologic.Vertex, optional
|
1757
|
+
The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0, 0, 0).
|
1758
|
+
widthA : float , optional
|
1759
|
+
The width of the bottom edge of the trapezoid. The default is 1.0.
|
1760
|
+
widthB : float , optional
|
1761
|
+
The width of the top edge of the trapezoid. The default is 0.75.
|
1762
|
+
offsetA : float , optional
|
1763
|
+
The offset of the bottom edge of the trapezoid. The default is 0.0.
|
1764
|
+
offsetB : float , optional
|
1765
|
+
The offset of the top edge of the trapezoid. The default is 0.0.
|
1766
|
+
length : float , optional
|
1767
|
+
The length of the trapezoid. The default is 1.0.
|
1768
|
+
direction : list , optional
|
1769
|
+
The vector representing the up direction of the trapezoid. The default is [0, 0, 1].
|
1770
|
+
placement : str , optional
|
1771
|
+
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".
|
1772
|
+
tolerance : float , optional
|
1773
|
+
The desired tolerance. The default is 0.0001.
|
1774
|
+
|
1775
|
+
Returns
|
1776
|
+
-------
|
1777
|
+
topologic.Face
|
1778
|
+
The created trapezoid.
|
1779
|
+
|
1780
|
+
"""
|
1781
|
+
from topologicpy.Wire import Wire
|
1782
|
+
wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
|
1783
|
+
if not isinstance(wire, topologic.Wire):
|
1784
|
+
print("Face.Rectangle - Error: Could not create the base wire for the trapezoid. Returning None.")
|
1785
|
+
return None
|
1786
|
+
return Face.ByWire(wire, tolerance=tolerance)
|
1787
|
+
|
1788
|
+
@staticmethod
|
1789
|
+
def Triangulate(face:topologic.Face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) -> list:
|
1790
|
+
"""
|
1791
|
+
Triangulates the input face and returns a list of faces.
|
1792
|
+
|
1793
|
+
Parameters
|
1794
|
+
----------
|
1795
|
+
face : topologic.Face
|
1796
|
+
The input face.
|
1797
|
+
tolerance : float , optional
|
1798
|
+
The desired tolerance. The default is 0.0001.
|
1799
|
+
mode : int , optional
|
1800
|
+
The desired mode of meshing algorithm. Several options are available:
|
1801
|
+
0: Classic
|
1802
|
+
1: MeshAdapt
|
1803
|
+
3: Initial Mesh Only
|
1804
|
+
5: Delaunay
|
1805
|
+
6: Frontal-Delaunay
|
1806
|
+
7: BAMG
|
1807
|
+
8: Fontal-Delaunay for Quads
|
1808
|
+
9: Packing of Parallelograms
|
1809
|
+
All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
|
1810
|
+
WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
|
1811
|
+
meshSize : float , optional
|
1812
|
+
The desired size of the mesh when using the "mesh" option. If set to None, it will be
|
1813
|
+
calculated automatically and set to 10% of the overall size of the face.
|
1814
|
+
|
1815
|
+
Returns
|
1816
|
+
-------
|
1817
|
+
list
|
1818
|
+
The list of triangles of the input face.
|
1819
|
+
|
1820
|
+
"""
|
1821
|
+
from topologicpy.Wire import Wire
|
1822
|
+
from topologicpy.Topology import Topology
|
1823
|
+
|
1824
|
+
# This function was contributed by Yidan Xue.
|
1825
|
+
def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001):
|
1826
|
+
"""
|
1827
|
+
Creates a gmsh of triangular meshes from the input face.
|
1828
|
+
|
1829
|
+
Parameters
|
1830
|
+
----------
|
1831
|
+
face : topologic.Face
|
1832
|
+
The input face.
|
1833
|
+
meshSize : float , optional
|
1834
|
+
The desired mesh size.
|
1835
|
+
tolerance : float , optional
|
1836
|
+
The desired tolerance. The default is 0.0001.
|
1837
|
+
|
1838
|
+
Returns
|
1839
|
+
----------
|
1840
|
+
topologic.Shell
|
1841
|
+
The shell of triangular meshes.
|
1842
|
+
|
1843
|
+
"""
|
1844
|
+
import os
|
1845
|
+
import warnings
|
1846
|
+
try:
|
1847
|
+
import numpy as np
|
1848
|
+
except:
|
1849
|
+
print("Face.Triangulate - Warning: Installing required numpy library.")
|
1850
|
+
try:
|
1851
|
+
os.system("pip install numpy")
|
1852
|
+
except:
|
1853
|
+
os.system("pip install numpy --user")
|
1854
|
+
try:
|
1855
|
+
import numpy as np
|
1856
|
+
print("Face.Triangulate - Warning: numpy library installed correctly.")
|
1857
|
+
except:
|
1858
|
+
warnings.warn("Face.Triangulate - Error: Could not import numpy. Please try to install numpy manually. Returning None.")
|
1859
|
+
return None
|
1860
|
+
try:
|
1861
|
+
import gmsh
|
1862
|
+
except:
|
1863
|
+
print("Face.Triangulate - Warning: Installing required gmsh library.")
|
1864
|
+
try:
|
1865
|
+
os.system("pip install gmsh")
|
1866
|
+
except:
|
1867
|
+
os.system("pip install gmsh --user")
|
1868
|
+
try:
|
1869
|
+
import gmsh
|
1870
|
+
print("Face.Triangulate - Warning: gmsh library installed correctly.")
|
1871
|
+
except:
|
1872
|
+
warnings.warn("Face.Triangulate - Error: Could not import gmsh. Please try to install gmsh manually. Returning None.")
|
1873
|
+
return None
|
1874
|
+
|
1875
|
+
import topologic_core as topologic
|
1876
|
+
from topologicpy.Vertex import Vertex
|
1877
|
+
from topologicpy.Wire import Wire
|
1878
|
+
from topologicpy.Face import Face
|
1879
|
+
|
1880
|
+
if not isinstance(face, topologic.Face):
|
1881
|
+
print("Shell.ByMeshFace - Error: The input face parameter is not a valid face. Returning None.")
|
1882
|
+
return None
|
1883
|
+
if not meshSize:
|
1884
|
+
bounding_face = Face.BoundingRectangle(face)
|
1885
|
+
bounding_face_vertices = Face.Vertices(bounding_face)
|
1886
|
+
bounding_face_vertices_x = [Vertex.X(i) for i in bounding_face_vertices]
|
1887
|
+
bounding_face_vertices_y = [Vertex.Y(i) for i in bounding_face_vertices]
|
1888
|
+
width = max(bounding_face_vertices_x)-min(bounding_face_vertices_x)
|
1889
|
+
length = max(bounding_face_vertices_y)-min(bounding_face_vertices_y)
|
1890
|
+
meshSize = max([width,length])//10
|
1891
|
+
|
1892
|
+
gmsh.initialize()
|
1893
|
+
face_external_boundary = Face.ExternalBoundary(face)
|
1894
|
+
external_vertices = Wire.Vertices(face_external_boundary)
|
1895
|
+
external_vertex_number = len(external_vertices)
|
1896
|
+
for i in range(external_vertex_number):
|
1897
|
+
gmsh.model.geo.addPoint(Vertex.X(external_vertices[i]), Vertex.Y(external_vertices[i]), Vertex.Z(external_vertices[i]), meshSize, i+1)
|
1898
|
+
for i in range(external_vertex_number):
|
1899
|
+
if i < external_vertex_number-1:
|
1900
|
+
gmsh.model.geo.addLine(i+1, i+2, i+1)
|
1901
|
+
else:
|
1902
|
+
gmsh.model.geo.addLine(i+1, 1, i+1)
|
1903
|
+
gmsh.model.geo.addCurveLoop([i+1 for i in range(external_vertex_number)], 1)
|
1904
|
+
current_vertex_number = external_vertex_number
|
1905
|
+
current_edge_number = external_vertex_number
|
1906
|
+
current_wire_number = 1
|
1907
|
+
|
1908
|
+
face_internal_boundaries = Face.InternalBoundaries(face)
|
1909
|
+
if face_internal_boundaries:
|
1910
|
+
internal_face_number = len(face_internal_boundaries)
|
1911
|
+
for i in range(internal_face_number):
|
1912
|
+
face_internal_boundary = face_internal_boundaries[i]
|
1913
|
+
internal_vertices = Wire.Vertices(face_internal_boundary)
|
1914
|
+
internal_vertex_number = len(internal_vertices)
|
1915
|
+
for j in range(internal_vertex_number):
|
1916
|
+
gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j]), Vertex.Z(internal_vertices[j]), meshSize, current_vertex_number+j+1)
|
1917
|
+
for j in range(internal_vertex_number):
|
1918
|
+
if j < internal_vertex_number-1:
|
1919
|
+
gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+j+2, current_edge_number+j+1)
|
1920
|
+
else:
|
1921
|
+
gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+1, current_edge_number+j+1)
|
1922
|
+
gmsh.model.geo.addCurveLoop([current_edge_number+i+1 for i in range(internal_vertex_number)], current_wire_number+1)
|
1923
|
+
current_vertex_number = current_vertex_number+internal_vertex_number
|
1924
|
+
current_edge_number = current_edge_number+internal_vertex_number
|
1925
|
+
current_wire_number = current_wire_number+1
|
1926
|
+
|
1927
|
+
gmsh.model.geo.addPlaneSurface([i+1 for i in range(current_wire_number)])
|
1928
|
+
gmsh.model.geo.synchronize()
|
1929
|
+
if mode not in [1,3,5,6,7,8,9]:
|
1930
|
+
mode = 6
|
1931
|
+
gmsh.option.setNumber("Mesh.Algorithm", mode)
|
1932
|
+
gmsh.model.mesh.generate(2) # For a 2D mesh
|
1933
|
+
nodeTags, nodeCoords, nodeParams = gmsh.model.mesh.getNodes(-1, -1)
|
1934
|
+
elemTypes, elemTags, elemNodeTags = gmsh.model.mesh.getElements(-1, -1)
|
1935
|
+
gmsh.finalize()
|
1936
|
+
|
1937
|
+
vertex_number = len(nodeTags)
|
1938
|
+
vertices = []
|
1939
|
+
for i in range(vertex_number):
|
1940
|
+
vertices.append(Vertex.ByCoordinates(nodeCoords[3*i],nodeCoords[3*i+1],nodeCoords[3*i+2]))
|
1941
|
+
|
1942
|
+
faces = []
|
1943
|
+
for n in range(len(elemTypes)):
|
1944
|
+
vn = elemTypes[n]+1
|
1945
|
+
et = elemTags[n]
|
1946
|
+
ent = elemNodeTags[n]
|
1947
|
+
if vn==3:
|
1948
|
+
for i in range(len(et)):
|
1949
|
+
face_vertices = []
|
1950
|
+
for j in range(vn):
|
1951
|
+
face_vertices.append(vertices[np.where(nodeTags==ent[i*vn+j])[0][0]])
|
1952
|
+
faces.append(Face.ByVertices(face_vertices))
|
1953
|
+
return faces
|
1954
|
+
|
1955
|
+
if not isinstance(face, topologic.Face):
|
1956
|
+
print("Face.Triangulate - Error: The input face parameter is not a valid face. Returning None.")
|
1957
|
+
return None
|
1958
|
+
vertices = Topology.Vertices(face)
|
1959
|
+
if len(vertices) == 3: # Already a triangle
|
1960
|
+
return [face]
|
1961
|
+
origin = Topology.Centroid(face)
|
1962
|
+
normal = Face.Normal(face)
|
1963
|
+
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
|
1964
|
+
|
1965
|
+
if mode == 0:
|
1966
|
+
shell_faces = []
|
1967
|
+
for i in range(0,5,1):
|
1968
|
+
try:
|
1969
|
+
_ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, shell_faces)
|
1970
|
+
break
|
1971
|
+
except:
|
1972
|
+
continue
|
1973
|
+
else:
|
1974
|
+
shell_faces = generate_gmsh(flatFace, mode = mode, meshSize = meshSize, tolerance = tolerance)
|
1975
|
+
|
1976
|
+
if len(shell_faces) < 1:
|
1977
|
+
return []
|
1978
|
+
finalFaces = []
|
1979
|
+
for f in shell_faces:
|
1980
|
+
f = Topology.Unflatten(f, origin=origin, direction=normal)
|
1981
|
+
if Face.Angle(face, f) > 90:
|
1982
|
+
wire = Face.ExternalBoundary(f)
|
1983
|
+
wire = Wire.Invert(wire)
|
1984
|
+
f = topologic.Face.ByExternalBoundary(wire)
|
1985
|
+
finalFaces.append(f)
|
1986
|
+
else:
|
1987
|
+
finalFaces.append(f)
|
1988
|
+
return finalFaces
|
1989
|
+
|
1990
|
+
@staticmethod
|
1991
|
+
def TrimByWire(face: topologic.Face, wire: topologic.Wire, reverse: bool = False) -> topologic.Face:
|
1992
|
+
"""
|
1993
|
+
Trims the input face by the input wire.
|
1994
|
+
|
1995
|
+
Parameters
|
1996
|
+
----------
|
1997
|
+
face : topologic.Face
|
1998
|
+
The input face.
|
1999
|
+
wire : topologic.Wire
|
2000
|
+
The input wire.
|
2001
|
+
reverse : bool , optional
|
2002
|
+
If set to True, the effect of the trim will be reversed. The default is False.
|
2003
|
+
|
2004
|
+
Returns
|
2005
|
+
-------
|
2006
|
+
topologic.Face
|
2007
|
+
The resulting trimmed face.
|
2008
|
+
|
2009
|
+
"""
|
2010
|
+
if not isinstance(face, topologic.Face):
|
2011
|
+
return None
|
2012
|
+
if not isinstance(wire, topologic.Wire):
|
2013
|
+
return face
|
2014
|
+
trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False)
|
2015
|
+
if reverse:
|
2016
|
+
trimmed_face = face.Difference(trimmed_face)
|
2017
|
+
return trimmed_face
|
2018
|
+
|
2019
|
+
@staticmethod
|
2020
|
+
def VertexByParameters(face: topologic.Face, u: float = 0.5, v: float = 0.5) -> topologic.Vertex:
|
2021
|
+
"""
|
2022
|
+
Creates a vertex at the *u* and *v* parameters of the input face.
|
2023
|
+
|
2024
|
+
Parameters
|
2025
|
+
----------
|
2026
|
+
face : topologic.Face
|
2027
|
+
The input face.
|
2028
|
+
u : float , optional
|
2029
|
+
The *u* parameter of the input face. The default is 0.5.
|
2030
|
+
v : float , optional
|
2031
|
+
The *v* parameter of the input face. The default is 0.5.
|
2032
|
+
|
2033
|
+
Returns
|
2034
|
+
-------
|
2035
|
+
vertex : topologic vertex
|
2036
|
+
The created vertex.
|
2037
|
+
|
2038
|
+
"""
|
2039
|
+
if not isinstance(face, topologic.Face):
|
2040
|
+
return None
|
2041
|
+
return topologic.FaceUtility.VertexAtParameters(face, u, v)
|
2042
|
+
|
2043
|
+
@staticmethod
|
2044
|
+
def VertexParameters(face: topologic.Face, vertex: topologic.Vertex, outputType: str = "uv", mantissa: int = 6) -> list:
|
2045
|
+
"""
|
2046
|
+
Returns the *u* and *v* parameters of the input face at the location of the input vertex.
|
2047
|
+
|
2048
|
+
Parameters
|
2049
|
+
----------
|
2050
|
+
face : topologic.Face
|
2051
|
+
The input face.
|
2052
|
+
vertex : topologic.Vertex
|
2053
|
+
The input vertex.
|
2054
|
+
outputType : string , optional
|
2055
|
+
The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
|
2056
|
+
mantissa : int , optional
|
2057
|
+
The desired length of the mantissa. The default is 6.
|
2058
|
+
|
2059
|
+
Returns
|
2060
|
+
-------
|
2061
|
+
list
|
2062
|
+
The list of *u* and/or *v* as specified by the outputType input.
|
2063
|
+
|
2064
|
+
"""
|
2065
|
+
if not isinstance(face, topologic.Face):
|
2066
|
+
return None
|
2067
|
+
if not isinstance(vertex, topologic.Vertex):
|
2068
|
+
return None
|
2069
|
+
params = topologic.FaceUtility.ParametersAtVertex(face, vertex)
|
2070
|
+
u = round(params[0], mantissa)
|
2071
|
+
v = round(params[1], mantissa)
|
2072
|
+
outputType = list(outputType.lower())
|
2073
|
+
returnResult = []
|
2074
|
+
for param in outputType:
|
2075
|
+
if param == "u":
|
2076
|
+
returnResult.append(u)
|
2077
|
+
elif param == "v":
|
2078
|
+
returnResult.append(v)
|
2079
|
+
return returnResult
|
2080
|
+
|
2081
|
+
@staticmethod
|
2082
|
+
def Vertices(face: topologic.Face) -> list:
|
2083
|
+
"""
|
2084
|
+
Returns the vertices of the input face.
|
2085
|
+
|
2086
|
+
Parameters
|
2087
|
+
----------
|
2088
|
+
face : topologic.Face
|
2089
|
+
The input face.
|
2090
|
+
|
2091
|
+
Returns
|
2092
|
+
-------
|
2093
|
+
list
|
2094
|
+
The list of vertices.
|
2095
|
+
|
2096
|
+
"""
|
2097
|
+
if not isinstance(face, topologic.Face):
|
2098
|
+
return None
|
2099
|
+
vertices = []
|
2100
|
+
_ = face.Vertices(None, vertices)
|
2101
|
+
return vertices
|
2102
|
+
|
2103
|
+
@staticmethod
|
2104
|
+
def Wire(face: topologic.Face) -> topologic.Wire:
|
2105
|
+
"""
|
2106
|
+
Returns the external boundary (closed wire) of the input face.
|
2107
|
+
|
2108
|
+
Parameters
|
2109
|
+
----------
|
2110
|
+
face : topologic.Face
|
2111
|
+
The input face.
|
2112
|
+
|
2113
|
+
Returns
|
2114
|
+
-------
|
2115
|
+
topologic.Wire
|
2116
|
+
The external boundary of the input face.
|
2117
|
+
|
2118
|
+
"""
|
2119
|
+
return face.ExternalBoundary()
|
2120
|
+
|
2121
|
+
@staticmethod
|
2122
|
+
def Wires(face: topologic.Face) -> list:
|
2123
|
+
"""
|
2124
|
+
Returns the wires of the input face.
|
2125
|
+
|
2126
|
+
Parameters
|
2127
|
+
----------
|
2128
|
+
face : topologic.Face
|
2129
|
+
The input face.
|
2130
|
+
|
2131
|
+
Returns
|
2132
|
+
-------
|
2133
|
+
list
|
2134
|
+
The list of wires.
|
2135
|
+
|
2136
|
+
"""
|
2137
|
+
if not isinstance(face, topologic.Face):
|
2138
|
+
return None
|
2139
|
+
wires = []
|
2140
|
+
_ = face.Wires(None, wires)
|
2141
|
+
return wires
|