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