topologicpy 0.4.8__py3-none-any.whl → 0.4.9__py3-none-any.whl

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