topologicpy 0.4.8__py3-none-any.whl → 0.4.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologicpy/Aperture.py +46 -0
- topologicpy/Cell.py +1780 -0
- topologicpy/CellComplex.py +791 -0
- topologicpy/Cluster.py +591 -0
- topologicpy/Color.py +157 -0
- topologicpy/Context.py +56 -0
- topologicpy/DGL.py +2661 -0
- topologicpy/Dictionary.py +470 -0
- topologicpy/Edge.py +855 -0
- topologicpy/EnergyModel.py +1052 -0
- topologicpy/Face.py +1810 -0
- topologicpy/Graph.py +3526 -0
- topologicpy/Graph_Export.py +858 -0
- topologicpy/Grid.py +338 -0
- topologicpy/Helper.py +182 -0
- topologicpy/Honeybee.py +424 -0
- topologicpy/Matrix.py +255 -0
- topologicpy/Neo4jGraph.py +311 -0
- topologicpy/Plotly.py +1396 -0
- topologicpy/Polyskel.py +524 -0
- topologicpy/Process.py +1368 -0
- topologicpy/SQL.py +48 -0
- topologicpy/Shell.py +1418 -0
- topologicpy/Speckle.py +433 -0
- topologicpy/Topology.py +5854 -0
- topologicpy/UnitTest.py +29 -0
- topologicpy/Vector.py +555 -0
- topologicpy/Vertex.py +714 -0
- topologicpy/Wire.py +2346 -0
- topologicpy/__init__.py +20 -0
- topologicpy/bin/linux/topologic/__init__.py +2 -0
- topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
- topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
- topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
- topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
- topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
- topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
- topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
- topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
- topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
- topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
- topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
- topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
- topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
- topologicpy/bin/windows/topologic/__init__.py +2 -0
- topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/METADATA +1 -1
- topologicpy-0.4.9.dist-info/RECORD +77 -0
- topologicpy-0.4.9.dist-info/top_level.txt +1 -0
- topologicpy-0.4.8.dist-info/RECORD +0 -5
- topologicpy-0.4.8.dist-info/top_level.txt +0 -1
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/LICENSE +0 -0
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/WHEEL +0 -0
topologicpy/Wire.py
ADDED
|
@@ -0,0 +1,2346 @@
|
|
|
1
|
+
from binascii import a2b_base64
|
|
2
|
+
from re import A
|
|
3
|
+
import topologicpy
|
|
4
|
+
import topologic
|
|
5
|
+
from topologicpy.Cluster import Cluster
|
|
6
|
+
from topologicpy.Topology import Topology
|
|
7
|
+
import math
|
|
8
|
+
import itertools
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
class Wire(topologic.Wire):
|
|
12
|
+
@staticmethod
|
|
13
|
+
def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) -> topologic.Wire:
|
|
14
|
+
"""
|
|
15
|
+
Returns a wire representing a bounding rectangle of the input topology. The returned wire contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting wire will become axis-aligned.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
topology : topologic.Topology
|
|
20
|
+
The input topology.
|
|
21
|
+
optimize : int , optional
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
topologic.Wire
|
|
27
|
+
The bounding rectangle of the input topology.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
from topologicpy.Vertex import Vertex
|
|
31
|
+
from topologicpy.Wire import Wire
|
|
32
|
+
from topologicpy.Face import Face
|
|
33
|
+
from topologicpy.Cluster import Cluster
|
|
34
|
+
from topologicpy.Topology import Topology
|
|
35
|
+
from topologicpy.Dictionary import Dictionary
|
|
36
|
+
from random import sample
|
|
37
|
+
|
|
38
|
+
def br(topology):
|
|
39
|
+
vertices = []
|
|
40
|
+
_ = topology.Vertices(None, vertices)
|
|
41
|
+
x = []
|
|
42
|
+
y = []
|
|
43
|
+
for aVertex in vertices:
|
|
44
|
+
x.append(aVertex.X())
|
|
45
|
+
y.append(aVertex.Y())
|
|
46
|
+
minX = min(x)
|
|
47
|
+
minY = min(y)
|
|
48
|
+
maxX = max(x)
|
|
49
|
+
maxY = max(y)
|
|
50
|
+
return [minX, minY, maxX, maxY]
|
|
51
|
+
|
|
52
|
+
if not isinstance(topology, topologic.Topology):
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
56
|
+
|
|
57
|
+
xTran = None
|
|
58
|
+
# Create a sample face
|
|
59
|
+
while not xTran:
|
|
60
|
+
vertices = Topology.SubTopologies(topology=topology, subTopologyType="vertex")
|
|
61
|
+
v = sample(vertices, 3)
|
|
62
|
+
w = Wire.ByVertices(v)
|
|
63
|
+
f = Face.ByWire(w)
|
|
64
|
+
f = Face.Flatten(f)
|
|
65
|
+
dictionary = Topology.Dictionary(f)
|
|
66
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
67
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
68
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
69
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
70
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
71
|
+
|
|
72
|
+
topology = Topology.Translate(topology, xTran*-1, yTran*-1, zTran*-1)
|
|
73
|
+
topology = Topology.Rotate(topology, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
74
|
+
topology = Topology.Rotate(topology, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
75
|
+
|
|
76
|
+
boundingRectangle = br(topology)
|
|
77
|
+
minX = boundingRectangle[0]
|
|
78
|
+
minY = boundingRectangle[1]
|
|
79
|
+
maxX = boundingRectangle[2]
|
|
80
|
+
maxY = boundingRectangle[3]
|
|
81
|
+
w = abs(maxX - minX)
|
|
82
|
+
l = abs(maxY - minY)
|
|
83
|
+
best_area = l*w
|
|
84
|
+
orig_area = best_area
|
|
85
|
+
best_z = 0
|
|
86
|
+
best_br = boundingRectangle
|
|
87
|
+
origin = Topology.Centroid(topology)
|
|
88
|
+
optimize = min(max(optimize, 0), 10)
|
|
89
|
+
if optimize > 0:
|
|
90
|
+
factor = (round(((11 - optimize)/30 + 0.57), 2))
|
|
91
|
+
flag = False
|
|
92
|
+
for n in range(10,0,-1):
|
|
93
|
+
if flag:
|
|
94
|
+
break
|
|
95
|
+
za = n
|
|
96
|
+
zb = 90+n
|
|
97
|
+
zc = n
|
|
98
|
+
for z in range(za,zb,zc):
|
|
99
|
+
if flag:
|
|
100
|
+
break
|
|
101
|
+
t = Topology.Rotate(topology, origin=origin, x=0,y=0,z=1, degree=z)
|
|
102
|
+
minX, minY, maxX, maxY = br(t)
|
|
103
|
+
w = abs(maxX - minX)
|
|
104
|
+
l = abs(maxY - minY)
|
|
105
|
+
area = l*w
|
|
106
|
+
if area < orig_area*factor:
|
|
107
|
+
best_area = area
|
|
108
|
+
best_z = z
|
|
109
|
+
best_br = [minX, minY, maxX, maxY]
|
|
110
|
+
flag = True
|
|
111
|
+
break
|
|
112
|
+
if area < best_area:
|
|
113
|
+
best_area = area
|
|
114
|
+
best_z = z
|
|
115
|
+
best_br = [minX, minY, maxX, maxY]
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
best_br = boundingRectangle
|
|
119
|
+
|
|
120
|
+
minX, minY, maxX, maxY = best_br
|
|
121
|
+
vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
|
|
122
|
+
vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
|
|
123
|
+
vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
|
|
124
|
+
vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)
|
|
125
|
+
|
|
126
|
+
boundingRectangle = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
|
|
127
|
+
boundingRectangle = Topology.Rotate(boundingRectangle, origin=origin, x=0,y=0,z=1, degree=-best_z)
|
|
128
|
+
boundingRectangle = Topology.Rotate(boundingRectangle, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
129
|
+
boundingRectangle = Topology.Rotate(boundingRectangle, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
130
|
+
boundingRectangle = Topology.Translate(boundingRectangle, xTran, yTran, zTran)
|
|
131
|
+
|
|
132
|
+
dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
|
|
133
|
+
boundingRectangle = Topology.SetDictionary(boundingRectangle, dictionary)
|
|
134
|
+
return boundingRectangle
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def ByEdges(edges: list) -> topologic.Wire:
|
|
138
|
+
"""
|
|
139
|
+
Creates a wire from the input list of edges.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
edges : list
|
|
144
|
+
The input list of edges.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
topologic.Wire
|
|
149
|
+
The created wire.
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
if not isinstance(edges, list):
|
|
153
|
+
return None
|
|
154
|
+
edgeList = [x for x in edges if isinstance(x, topologic.Edge)]
|
|
155
|
+
if len(edgeList) < 1:
|
|
156
|
+
return None
|
|
157
|
+
wire = None
|
|
158
|
+
for anEdge in edgeList:
|
|
159
|
+
if anEdge.Type() == 2:
|
|
160
|
+
if wire == None:
|
|
161
|
+
wire = topologic.Wire.ByEdges([anEdge])
|
|
162
|
+
else:
|
|
163
|
+
try:
|
|
164
|
+
wire = wire.Merge(anEdge)
|
|
165
|
+
except:
|
|
166
|
+
continue
|
|
167
|
+
if wire.Type() != 4:
|
|
168
|
+
wire = None
|
|
169
|
+
return wire
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def ByEdgesCluster(cluster: topologic.Cluster) -> topologic.Wire:
|
|
173
|
+
"""
|
|
174
|
+
Creates a wire from the input cluster of edges.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
cluster : topologic.Cluster
|
|
179
|
+
The input cluster of edges.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
topologic.Wire
|
|
184
|
+
The created wire.
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
188
|
+
return None
|
|
189
|
+
edges = []
|
|
190
|
+
_ = cluster.Edges(None, edges)
|
|
191
|
+
return Wire.ByEdges(edges)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def ByOffset(wire: topologic.Wire, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) -> topologic.Wire:
|
|
195
|
+
"""
|
|
196
|
+
Creates an offset wire from the input wire.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
wire : topologic.Wire
|
|
201
|
+
The input wire.
|
|
202
|
+
offset : float , optional
|
|
203
|
+
The desired offset distance. The default is 1.0.
|
|
204
|
+
miter : bool , optional
|
|
205
|
+
if set to True, the corners will be mitered. The default is False.
|
|
206
|
+
miterThreshold : float , optional
|
|
207
|
+
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.
|
|
208
|
+
offsetKey : str , optional
|
|
209
|
+
If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
|
|
210
|
+
miterThresholdKey : str , optional
|
|
211
|
+
If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
|
|
212
|
+
step : bool , optional
|
|
213
|
+
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.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
topologic.Wire
|
|
218
|
+
The created wire.
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
from topologicpy.Vertex import Vertex
|
|
222
|
+
from topologicpy.Edge import Edge
|
|
223
|
+
from topologicpy.Face import Face
|
|
224
|
+
from topologicpy.Shell import Shell
|
|
225
|
+
from topologicpy.Cluster import Cluster
|
|
226
|
+
from topologicpy.Dictionary import Dictionary
|
|
227
|
+
from topologicpy.Vector import Vector
|
|
228
|
+
from random import randrange, sample
|
|
229
|
+
|
|
230
|
+
if not isinstance(wire, topologic.Wire):
|
|
231
|
+
return None
|
|
232
|
+
if not miterThreshold:
|
|
233
|
+
miterThreshold = offset*math.sqrt(2)
|
|
234
|
+
flatFace = Face.ByWire(wire)
|
|
235
|
+
flatFace = Face.Flatten(flatFace)
|
|
236
|
+
|
|
237
|
+
world_origin = Vertex.ByCoordinates(0,0,0)
|
|
238
|
+
# Retrieve the needed transformations
|
|
239
|
+
dictionary = Topology.Dictionary(flatFace)
|
|
240
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
241
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
242
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
243
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
244
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
edges = Wire.Edges(wire)
|
|
249
|
+
vertices = Wire.Vertices(wire)
|
|
250
|
+
flatEdges = []
|
|
251
|
+
flatVertices = []
|
|
252
|
+
newEdges = []
|
|
253
|
+
for i in range(len(vertices)):
|
|
254
|
+
flatVertex = Topology.Translate(vertices[i], -xTran, -yTran, -zTran)
|
|
255
|
+
flatVertex = Topology.Rotate(flatVertex, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
256
|
+
flatVertex = Topology.Rotate(flatVertex, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
257
|
+
flatVertices.append(flatVertex)
|
|
258
|
+
vertices = flatVertices
|
|
259
|
+
for i in range(len(edges)):
|
|
260
|
+
flatEdge = Topology.Translate(edges[i], -xTran, -yTran, -zTran)
|
|
261
|
+
flatEdge = Topology.Rotate(flatEdge, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
262
|
+
flatEdge = Topology.Rotate(flatEdge, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
263
|
+
flatEdges.append(flatEdge)
|
|
264
|
+
if offsetKey:
|
|
265
|
+
d = Topology.Dictionary(edges[i])
|
|
266
|
+
value = Dictionary.ValueAtKey(d, key=offsetKey)
|
|
267
|
+
c = Topology.Centroid(flatEdge)
|
|
268
|
+
if value:
|
|
269
|
+
finalOffset = value
|
|
270
|
+
else:
|
|
271
|
+
finalOffset = offset
|
|
272
|
+
else:
|
|
273
|
+
finalOffset = offset
|
|
274
|
+
e1 = Edge.ByOffset2D(flatEdge,finalOffset)
|
|
275
|
+
newEdges.append(e1)
|
|
276
|
+
edges = flatEdges
|
|
277
|
+
newVertices = []
|
|
278
|
+
dupVertices = []
|
|
279
|
+
if Wire.IsClosed(wire):
|
|
280
|
+
e1 = newEdges[-1]
|
|
281
|
+
e2 = newEdges[0]
|
|
282
|
+
intV = Edge.Intersect2D(e1,e2)
|
|
283
|
+
if intV:
|
|
284
|
+
newVertices.append(intV)
|
|
285
|
+
dupVertices.append(vertices[0])
|
|
286
|
+
elif step:
|
|
287
|
+
edgeVertices= Edge.Vertices(e1)
|
|
288
|
+
newVertices.append(Vertex.NearestVertex(vertices[-1], Cluster.ByTopologies(edgeVertices), useKDTree=False))
|
|
289
|
+
edgeVertices= Edge.Vertices(e2)
|
|
290
|
+
newVertices.append(Vertex.NearestVertex(vertices[0], Cluster.ByTopologies(edgeVertices), useKDTree=False))
|
|
291
|
+
dupVertices.append(vertices[0])
|
|
292
|
+
dupVertices.append(vertices[0])
|
|
293
|
+
else:
|
|
294
|
+
tempEdge1 = Edge.ByVertices([Edge.StartVertex(e1), Edge.EndVertex(e2)])
|
|
295
|
+
normal = Edge.Normal(e1)
|
|
296
|
+
normal = [normal[0]*finalOffset*10, normal[1]*finalOffset*10, normal[2]*finalOffset*10]
|
|
297
|
+
tempV = Vertex.ByCoordinates(vertices[0].X()+normal[0], vertices[0].Y()+normal[1], vertices[0].Z()+normal[2])
|
|
298
|
+
tempEdge2 = Edge.ByVertices([vertices[0], tempV])
|
|
299
|
+
intV = Edge.Intersect2D(tempEdge1,tempEdge2)
|
|
300
|
+
newVertices.append(intV)
|
|
301
|
+
dupVertices.append(vertices[0])
|
|
302
|
+
else:
|
|
303
|
+
newVertices.append(Edge.StartVertex(newEdges[0]))
|
|
304
|
+
|
|
305
|
+
for i in range(len(newEdges)-1):
|
|
306
|
+
e1 = newEdges[i]
|
|
307
|
+
e2 = newEdges[i+1]
|
|
308
|
+
intV = Edge.Intersect2D(e1,e2)
|
|
309
|
+
if intV:
|
|
310
|
+
newVertices.append(intV)
|
|
311
|
+
dupVertices.append(vertices[i+1])
|
|
312
|
+
elif step:
|
|
313
|
+
newVertices.append(Edge.EndVertex(e1))
|
|
314
|
+
newVertices.append(Edge.StartVertex(e2))
|
|
315
|
+
dupVertices.append(vertices[i+1])
|
|
316
|
+
dupVertices.append(vertices[i+1])
|
|
317
|
+
else:
|
|
318
|
+
tempEdge1 = Edge.ByVertices([Edge.StartVertex(e1), Edge.EndVertex(e2)])
|
|
319
|
+
normal = Edge.Normal(e1)
|
|
320
|
+
normal = [normal[0]*finalOffset*10, normal[1]*finalOffset*10, normal[2]*finalOffset*10]
|
|
321
|
+
tempV = Vertex.ByCoordinates(vertices[i+1].X()+normal[0], vertices[i+1].Y()+normal[1], vertices[i+1].Z()+normal[2])
|
|
322
|
+
tempEdge2 = Edge.ByVertices([vertices[i+1], tempV])
|
|
323
|
+
intV = Edge.Intersect2D(tempEdge1,tempEdge2)
|
|
324
|
+
newVertices.append(intV)
|
|
325
|
+
dupVertices.append(vertices[i+1])
|
|
326
|
+
|
|
327
|
+
vertices = dupVertices
|
|
328
|
+
if not Wire.IsClosed(wire):
|
|
329
|
+
newVertices.append(Edge.EndVertex(newEdges[-1]))
|
|
330
|
+
newWire = Wire.ByVertices(newVertices, close=Wire.IsClosed(wire))
|
|
331
|
+
|
|
332
|
+
newVertices = Wire.Vertices(newWire)
|
|
333
|
+
newEdges = Wire.Edges(newWire)
|
|
334
|
+
miterEdges = []
|
|
335
|
+
cleanMiterEdges = []
|
|
336
|
+
# Handle miter
|
|
337
|
+
if miter:
|
|
338
|
+
for i in range(len(newVertices)):
|
|
339
|
+
if miterThresholdKey:
|
|
340
|
+
d = Topology.Dictionary(vertices[i])
|
|
341
|
+
value = Dictionary.ValueAtKey(d, key=miterThresholdKey)
|
|
342
|
+
if value:
|
|
343
|
+
finalMiterThreshold = value
|
|
344
|
+
else:
|
|
345
|
+
finalMiterThreshold = miterThreshold
|
|
346
|
+
else:
|
|
347
|
+
finalMiterThreshold = miterThreshold
|
|
348
|
+
if Vertex.Distance(vertices[i], newVertices[i]) > abs(finalMiterThreshold):
|
|
349
|
+
st = Topology.SuperTopologies(newVertices[i], newWire, topologyType="edge")
|
|
350
|
+
if len(st) > 1:
|
|
351
|
+
e1 = st[0]
|
|
352
|
+
e2 = st[1]
|
|
353
|
+
if not Edge.IsCollinear(e1, e2):
|
|
354
|
+
e1 = Edge.Reverse(e1)
|
|
355
|
+
bisector = Edge.ByVertices([vertices[i], newVertices[i]])
|
|
356
|
+
nv = Edge.VertexByDistance(bisector, distance=finalMiterThreshold, origin=Edge.StartVertex(bisector), tolerance=0.0001)
|
|
357
|
+
vec = Edge.Normal2D(bisector)
|
|
358
|
+
nv2 = Topology.Translate(nv, vec[0], vec[1], 0)
|
|
359
|
+
nv3 = Topology.Translate(nv, -vec[0], -vec[1], 0)
|
|
360
|
+
miterEdge = Edge.ByVertices([nv2,nv3])
|
|
361
|
+
if miterEdge:
|
|
362
|
+
miterEdge = Edge.SetLength(miterEdge, abs(offset)*10)
|
|
363
|
+
msv = Edge.Intersect2D(miterEdge, e1)
|
|
364
|
+
mev = Edge.Intersect2D(miterEdge, e2)
|
|
365
|
+
if (Topology.IsInside(e1, msv,tolerance=0.01) and (Topology.IsInside(e2, mev, tolerance=0.01))):
|
|
366
|
+
miterEdge = Edge.ByVertices([msv, mev])
|
|
367
|
+
if miterEdge:
|
|
368
|
+
cleanMiterEdges.append(miterEdge)
|
|
369
|
+
miterEdge = Edge.SetLength(miterEdge, Edge.Length(miterEdge)*1.02)
|
|
370
|
+
miterEdges.append(miterEdge)
|
|
371
|
+
|
|
372
|
+
c = Cluster.SelfMerge(Cluster.ByTopologies(newEdges+miterEdges))
|
|
373
|
+
vertices = Wire.Vertices(c)
|
|
374
|
+
subtractEdges = []
|
|
375
|
+
for v in vertices:
|
|
376
|
+
edges = Topology.SuperTopologies(v, c, topologyType="edge")
|
|
377
|
+
if len(edges) == 2:
|
|
378
|
+
if not Edge.IsCollinear(edges[0], edges[1]):
|
|
379
|
+
adjacentVertices = Topology.AdjacentTopologies(v, c)
|
|
380
|
+
total = 0
|
|
381
|
+
for adjV in adjacentVertices:
|
|
382
|
+
tempEdges = Topology.SuperTopologies(adjV, c, topologyType="edge")
|
|
383
|
+
total += len(tempEdges)
|
|
384
|
+
if total == 8:
|
|
385
|
+
subtractEdges = subtractEdges+edges
|
|
386
|
+
|
|
387
|
+
if len(subtractEdges) > 0:
|
|
388
|
+
newWire = Topology.Boolean(newWire, Cluster.ByTopologies(subtractEdges), operation="difference")
|
|
389
|
+
if len(cleanMiterEdges) > 0:
|
|
390
|
+
newWire = Topology.Boolean(newWire, Cluster.ByTopologies(cleanMiterEdges), operation="merge")
|
|
391
|
+
|
|
392
|
+
newWire = Topology.Rotate(newWire, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
393
|
+
newWire = Topology.Rotate(newWire, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
394
|
+
newWire = Topology.Translate(newWire, xTran, yTran, zTran)
|
|
395
|
+
return newWire
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def ByVertices(vertices: list, close: bool = True) -> topologic.Wire:
|
|
399
|
+
"""
|
|
400
|
+
Creates a wire from the input list of vertices.
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
vertices : list
|
|
405
|
+
the input list of vertices.
|
|
406
|
+
close : bool , optional
|
|
407
|
+
If True the last vertex will be connected to the first vertex to close the wire. The default is True.
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
topologic.Wire
|
|
412
|
+
The created wire.
|
|
413
|
+
|
|
414
|
+
"""
|
|
415
|
+
from topologicpy.Cluster import Cluster
|
|
416
|
+
if not isinstance(vertices, list):
|
|
417
|
+
return None
|
|
418
|
+
vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
|
|
419
|
+
if len(vertexList) < 2:
|
|
420
|
+
return None
|
|
421
|
+
edges = []
|
|
422
|
+
for i in range(len(vertexList)-1):
|
|
423
|
+
v1 = vertexList[i]
|
|
424
|
+
v2 = vertexList[i+1]
|
|
425
|
+
try:
|
|
426
|
+
e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
|
|
427
|
+
if e:
|
|
428
|
+
edges.append(e)
|
|
429
|
+
except:
|
|
430
|
+
continue
|
|
431
|
+
if close:
|
|
432
|
+
v1 = vertexList[-1]
|
|
433
|
+
v2 = vertexList[0]
|
|
434
|
+
try:
|
|
435
|
+
e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
|
|
436
|
+
if e:
|
|
437
|
+
edges.append(e)
|
|
438
|
+
except:
|
|
439
|
+
pass
|
|
440
|
+
if len(edges) < 1:
|
|
441
|
+
return None
|
|
442
|
+
#return Wire.ByEdges(edges)
|
|
443
|
+
c = Cluster.ByTopologies(edges)
|
|
444
|
+
return Cluster.SelfMerge(c)
|
|
445
|
+
|
|
446
|
+
@staticmethod
|
|
447
|
+
def ByVerticesCluster(cluster: topologic.Cluster, close: bool = True) -> topologic.Wire:
|
|
448
|
+
"""
|
|
449
|
+
Creates a wire from the input cluster of vertices.
|
|
450
|
+
|
|
451
|
+
Parameters
|
|
452
|
+
----------
|
|
453
|
+
cluster : topologic.cluster
|
|
454
|
+
the input cluster of vertices.
|
|
455
|
+
close : bool , optional
|
|
456
|
+
If True the last vertex will be connected to the first vertex to close the wire. The default is True.
|
|
457
|
+
|
|
458
|
+
Returns
|
|
459
|
+
-------
|
|
460
|
+
topologic.Wire
|
|
461
|
+
The created wire.
|
|
462
|
+
|
|
463
|
+
"""
|
|
464
|
+
if not isinstance(cluster, topologic.Cluster):
|
|
465
|
+
return None
|
|
466
|
+
vertices = []
|
|
467
|
+
_ = cluster.Vertices(None, vertices)
|
|
468
|
+
return Wire.ByVertices(vertices, close)
|
|
469
|
+
|
|
470
|
+
@staticmethod
|
|
471
|
+
def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
|
|
472
|
+
"""
|
|
473
|
+
Creates a circle.
|
|
474
|
+
|
|
475
|
+
Parameters
|
|
476
|
+
----------
|
|
477
|
+
origin : topologic.Vertex , optional
|
|
478
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
|
|
479
|
+
radius : float , optional
|
|
480
|
+
The radius of the circle. The default is 0.5.
|
|
481
|
+
sides : int , optional
|
|
482
|
+
The number of sides of the circle. The default is 16.
|
|
483
|
+
fromAngle : float , optional
|
|
484
|
+
The angle in degrees from which to start creating the arc of the circle. The default is 0.
|
|
485
|
+
toAngle : float , optional
|
|
486
|
+
The angle in degrees at which to end creating the arc of the circle. The default is 360.
|
|
487
|
+
close : bool , optional
|
|
488
|
+
If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
|
|
489
|
+
direction : list , optional
|
|
490
|
+
The vector representing the up direction of the circle. The default is [0,0,1].
|
|
491
|
+
placement : str , optional
|
|
492
|
+
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".
|
|
493
|
+
tolerance : float , optional
|
|
494
|
+
The desired tolerance. The default is 0.0001.
|
|
495
|
+
|
|
496
|
+
Returns
|
|
497
|
+
-------
|
|
498
|
+
topologic.Wire
|
|
499
|
+
The created circle.
|
|
500
|
+
|
|
501
|
+
"""
|
|
502
|
+
if not origin:
|
|
503
|
+
origin = topologic.Vertex.ByCoordinates(0,0,0)
|
|
504
|
+
if not isinstance(origin, topologic.Vertex):
|
|
505
|
+
return None
|
|
506
|
+
if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
|
|
507
|
+
return None
|
|
508
|
+
radius = abs(radius)
|
|
509
|
+
if radius < tolerance:
|
|
510
|
+
return None
|
|
511
|
+
|
|
512
|
+
if (abs(direction[0]) + abs(direction[1]) + abs(direction[2])) < tolerance:
|
|
513
|
+
return None
|
|
514
|
+
baseV = []
|
|
515
|
+
xList = []
|
|
516
|
+
yList = []
|
|
517
|
+
|
|
518
|
+
if toAngle < fromAngle:
|
|
519
|
+
toAngle += 360
|
|
520
|
+
if abs(toAngle-fromAngle) < tolerance:
|
|
521
|
+
return None
|
|
522
|
+
angleRange = toAngle - fromAngle
|
|
523
|
+
fromAngle = math.radians(fromAngle)
|
|
524
|
+
toAngle = math.radians(toAngle)
|
|
525
|
+
sides = int(math.floor(sides))
|
|
526
|
+
for i in range(sides+1):
|
|
527
|
+
angle = fromAngle + math.radians(angleRange/sides)*i
|
|
528
|
+
x = math.sin(angle)*radius + origin.X()
|
|
529
|
+
y = math.cos(angle)*radius + origin.Y()
|
|
530
|
+
z = origin.Z()
|
|
531
|
+
xList.append(x)
|
|
532
|
+
yList.append(y)
|
|
533
|
+
baseV.append(topologic.Vertex.ByCoordinates(x,y,z))
|
|
534
|
+
|
|
535
|
+
baseWire = Wire.ByVertices(baseV[::-1], close) #reversing the list so that the normal points up in Blender
|
|
536
|
+
|
|
537
|
+
if placement.lower() == "lowerleft":
|
|
538
|
+
baseWire = topologic.TopologyUtility.Translate(baseWire, radius, radius, 0)
|
|
539
|
+
elif placement.lower() == "upperleft":
|
|
540
|
+
baseWire = topologic.TopologyUtility.Translate(baseWire, radius, -radius, 0)
|
|
541
|
+
elif placement.lower() == "lowerright":
|
|
542
|
+
baseWire = topologic.TopologyUtility.Translate(baseWire, -radius, radius, 0)
|
|
543
|
+
elif placement.lower() == "upperright":
|
|
544
|
+
baseWire = topologic.TopologyUtility.Translate(baseWire, -radius, -radius, 0)
|
|
545
|
+
x1 = origin.X()
|
|
546
|
+
y1 = origin.Y()
|
|
547
|
+
z1 = origin.Z()
|
|
548
|
+
x2 = origin.X() + direction[0]
|
|
549
|
+
y2 = origin.Y() + direction[1]
|
|
550
|
+
z2 = origin.Z() + direction[2]
|
|
551
|
+
dx = x2 - x1
|
|
552
|
+
dy = y2 - y1
|
|
553
|
+
dz = z2 - z1
|
|
554
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
555
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
556
|
+
if dist < 0.0001:
|
|
557
|
+
theta = 0
|
|
558
|
+
else:
|
|
559
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
560
|
+
baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 1, 0, theta)
|
|
561
|
+
baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 0, 1, phi)
|
|
562
|
+
return baseWire
|
|
563
|
+
|
|
564
|
+
@staticmethod
|
|
565
|
+
def ConvexHull(topology):
|
|
566
|
+
"""
|
|
567
|
+
Returns a wire representing the 2D convex hull of the input topology. The vertices of the topology are assumed to be coplanar.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
topology : topologic.Topology
|
|
572
|
+
The input topology.
|
|
573
|
+
|
|
574
|
+
Returns
|
|
575
|
+
-------
|
|
576
|
+
topologic.Wire
|
|
577
|
+
The convex hull of the input topology.
|
|
578
|
+
|
|
579
|
+
"""
|
|
580
|
+
from topologicpy.Vertex import Vertex
|
|
581
|
+
from topologicpy.Face import Face
|
|
582
|
+
from topologicpy.Topology import Topology
|
|
583
|
+
from topologicpy.Dictionary import Dictionary
|
|
584
|
+
from random import sample
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def Left_index(points):
|
|
588
|
+
|
|
589
|
+
'''
|
|
590
|
+
Finding the left most point
|
|
591
|
+
'''
|
|
592
|
+
minn = 0
|
|
593
|
+
for i in range(1,len(points)):
|
|
594
|
+
if points[i][0] < points[minn][0]:
|
|
595
|
+
minn = i
|
|
596
|
+
elif points[i][0] == points[minn][0]:
|
|
597
|
+
if points[i][1] > points[minn][1]:
|
|
598
|
+
minn = i
|
|
599
|
+
return minn
|
|
600
|
+
|
|
601
|
+
def orientation(p, q, r):
|
|
602
|
+
'''
|
|
603
|
+
To find orientation of ordered triplet (p, q, r).
|
|
604
|
+
The function returns following values
|
|
605
|
+
0 --> p, q and r are collinear
|
|
606
|
+
1 --> Clockwise
|
|
607
|
+
2 --> Counterclockwise
|
|
608
|
+
'''
|
|
609
|
+
val = (q[1] - p[1]) * (r[0] - q[0]) - \
|
|
610
|
+
(q[0] - p[0]) * (r[1] - q[1])
|
|
611
|
+
|
|
612
|
+
if val == 0:
|
|
613
|
+
return 0
|
|
614
|
+
elif val > 0:
|
|
615
|
+
return 1
|
|
616
|
+
else:
|
|
617
|
+
return 2
|
|
618
|
+
|
|
619
|
+
def convex_hull(points, n):
|
|
620
|
+
|
|
621
|
+
# There must be at least 3 points
|
|
622
|
+
if n < 3:
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
# Find the leftmost point
|
|
626
|
+
l = Left_index(points)
|
|
627
|
+
|
|
628
|
+
hull = []
|
|
629
|
+
|
|
630
|
+
'''
|
|
631
|
+
Start from leftmost point, keep moving counterclockwise
|
|
632
|
+
until reach the start point again. This loop runs O(h)
|
|
633
|
+
times where h is number of points in result or output.
|
|
634
|
+
'''
|
|
635
|
+
p = l
|
|
636
|
+
q = 0
|
|
637
|
+
while(True):
|
|
638
|
+
|
|
639
|
+
# Add current point to result
|
|
640
|
+
hull.append(p)
|
|
641
|
+
|
|
642
|
+
'''
|
|
643
|
+
Search for a point 'q' such that orientation(p, q,
|
|
644
|
+
x) is counterclockwise for all points 'x'. The idea
|
|
645
|
+
is to keep track of last visited most counterclock-
|
|
646
|
+
wise point in q. If any point 'i' is more counterclock-
|
|
647
|
+
wise than q, then update q.
|
|
648
|
+
'''
|
|
649
|
+
q = (p + 1) % n
|
|
650
|
+
|
|
651
|
+
for i in range(n):
|
|
652
|
+
|
|
653
|
+
# If i is more counterclockwise
|
|
654
|
+
# than current q, then update q
|
|
655
|
+
if(orientation(points[p],
|
|
656
|
+
points[i], points[q]) == 2):
|
|
657
|
+
q = i
|
|
658
|
+
|
|
659
|
+
'''
|
|
660
|
+
Now q is the most counterclockwise with respect to p
|
|
661
|
+
Set p as q for next iteration, so that q is added to
|
|
662
|
+
result 'hull'
|
|
663
|
+
'''
|
|
664
|
+
p = q
|
|
665
|
+
|
|
666
|
+
# While we don't come to first point
|
|
667
|
+
if(p == l):
|
|
668
|
+
break
|
|
669
|
+
|
|
670
|
+
# Print Result
|
|
671
|
+
return hull
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
xTran = None
|
|
675
|
+
# Create a sample face and flatten
|
|
676
|
+
while not xTran:
|
|
677
|
+
vertices = Topology.SubTopologies(topology=topology, subTopologyType="vertex")
|
|
678
|
+
v = sample(vertices, 3)
|
|
679
|
+
w = Wire.ByVertices(v)
|
|
680
|
+
f = Face.ByWire(w)
|
|
681
|
+
f = Face.Flatten(f)
|
|
682
|
+
dictionary = Topology.Dictionary(f)
|
|
683
|
+
xTran = Dictionary.ValueAtKey(dictionary,"xTran")
|
|
684
|
+
yTran = Dictionary.ValueAtKey(dictionary,"yTran")
|
|
685
|
+
zTran = Dictionary.ValueAtKey(dictionary,"zTran")
|
|
686
|
+
phi = Dictionary.ValueAtKey(dictionary,"phi")
|
|
687
|
+
theta = Dictionary.ValueAtKey(dictionary,"theta")
|
|
688
|
+
|
|
689
|
+
world_origin = Vertex.Origin()
|
|
690
|
+
topology = Topology.Translate(topology, xTran*-1, yTran*-1, zTran*-1)
|
|
691
|
+
topology = Topology.Rotate(topology, origin=world_origin, x=0, y=0, z=1, degree=-phi)
|
|
692
|
+
topology = Topology.Rotate(topology, origin=world_origin, x=0, y=1, z=0, degree=-theta)
|
|
693
|
+
|
|
694
|
+
vertices = Topology.Vertices(topology)
|
|
695
|
+
|
|
696
|
+
points = []
|
|
697
|
+
for v in vertices:
|
|
698
|
+
points.append((Vertex.X(v), Vertex.Y(v)))
|
|
699
|
+
hull = convex_hull(points, len(points))
|
|
700
|
+
|
|
701
|
+
hull_vertices = []
|
|
702
|
+
for p in hull:
|
|
703
|
+
hull_vertices.append(Vertex.ByCoordinates(points[p][0], points[p][1], 0))
|
|
704
|
+
|
|
705
|
+
ch = Wire.ByVertices(hull_vertices)
|
|
706
|
+
ch = Topology.Rotate(ch, origin=world_origin, x=0, y=1, z=0, degree=theta)
|
|
707
|
+
ch = Topology.Rotate(ch, origin=world_origin, x=0, y=0, z=1, degree=phi)
|
|
708
|
+
ch = Topology.Translate(ch, xTran, yTran, zTran)
|
|
709
|
+
return ch
|
|
710
|
+
|
|
711
|
+
@staticmethod
|
|
712
|
+
def Cycles(wire: topologic.Wire, maxVertices: int = 4, tolerance: float = 0.0001) -> list:
|
|
713
|
+
"""
|
|
714
|
+
Returns the closed circuits of wires found within the input wire.
|
|
715
|
+
|
|
716
|
+
Parameters
|
|
717
|
+
----------
|
|
718
|
+
wire : topologic.Wire
|
|
719
|
+
The input wire.
|
|
720
|
+
maxVertices : int , optional
|
|
721
|
+
The maximum number of vertices of the circuits to be searched. The default is 4.
|
|
722
|
+
tolerance : float , optional
|
|
723
|
+
The desired tolerance. The default is 0.0001.
|
|
724
|
+
|
|
725
|
+
Returns
|
|
726
|
+
-------
|
|
727
|
+
list
|
|
728
|
+
The list of circuits (closed wires) found within the input wire.
|
|
729
|
+
|
|
730
|
+
"""
|
|
731
|
+
|
|
732
|
+
def vIndex(v, vList, tolerance):
|
|
733
|
+
for i in range(len(vList)):
|
|
734
|
+
if topologic.VertexUtility.Distance(v, vList[i]) < tolerance:
|
|
735
|
+
return i+1
|
|
736
|
+
return None
|
|
737
|
+
|
|
738
|
+
# rotate cycle path such that it begins with the smallest node
|
|
739
|
+
def rotate_to_smallest(path):
|
|
740
|
+
n = path.index(min(path))
|
|
741
|
+
return path[n:]+path[:n]
|
|
742
|
+
|
|
743
|
+
def invert(path):
|
|
744
|
+
return rotate_to_smallest(path[::-1])
|
|
745
|
+
|
|
746
|
+
def isNew(cycles, path):
|
|
747
|
+
return not path in cycles
|
|
748
|
+
|
|
749
|
+
def visited(node, path):
|
|
750
|
+
return node in path
|
|
751
|
+
|
|
752
|
+
def findNewCycles(graph, cycles, path, maxVertices):
|
|
753
|
+
if len(path) > maxVertices:
|
|
754
|
+
return
|
|
755
|
+
start_node = path[0]
|
|
756
|
+
next_node= None
|
|
757
|
+
sub = []
|
|
758
|
+
|
|
759
|
+
#visit each edge and each node of each edge
|
|
760
|
+
for edge in graph:
|
|
761
|
+
node1, node2 = edge
|
|
762
|
+
if start_node in edge:
|
|
763
|
+
if node1 == start_node:
|
|
764
|
+
next_node = node2
|
|
765
|
+
else:
|
|
766
|
+
next_node = node1
|
|
767
|
+
if not visited(next_node, path):
|
|
768
|
+
# neighbor node not on path yet
|
|
769
|
+
sub = [next_node]
|
|
770
|
+
sub.extend(path)
|
|
771
|
+
# explore extended path
|
|
772
|
+
findNewCycles(graph, cycles, sub, maxVertices);
|
|
773
|
+
elif len(path) > 2 and next_node == path[-1]:
|
|
774
|
+
# cycle found
|
|
775
|
+
p = rotate_to_smallest(path);
|
|
776
|
+
inv = invert(p)
|
|
777
|
+
if isNew(cycles, p) and isNew(cycles, inv):
|
|
778
|
+
cycles.append(p)
|
|
779
|
+
|
|
780
|
+
def main(graph, cycles, maxVertices):
|
|
781
|
+
returnValue = []
|
|
782
|
+
for edge in graph:
|
|
783
|
+
for node in edge:
|
|
784
|
+
findNewCycles(graph, cycles, [node], maxVertices)
|
|
785
|
+
for cy in cycles:
|
|
786
|
+
row = []
|
|
787
|
+
for node in cy:
|
|
788
|
+
row.append(node)
|
|
789
|
+
returnValue.append(row)
|
|
790
|
+
return returnValue
|
|
791
|
+
|
|
792
|
+
tEdges = []
|
|
793
|
+
_ = wire.Edges(None, tEdges)
|
|
794
|
+
tVertices = []
|
|
795
|
+
_ = wire.Vertices(None, tVertices)
|
|
796
|
+
tVertices = tVertices
|
|
797
|
+
|
|
798
|
+
graph = []
|
|
799
|
+
for anEdge in tEdges:
|
|
800
|
+
graph.append([vIndex(anEdge.StartVertex(), tVertices, tolerance), vIndex(anEdge.EndVertex(), tVertices, tolerance)])
|
|
801
|
+
|
|
802
|
+
cycles = []
|
|
803
|
+
resultingCycles = main(graph, cycles, maxVertices)
|
|
804
|
+
|
|
805
|
+
result = []
|
|
806
|
+
for aRow in resultingCycles:
|
|
807
|
+
row = []
|
|
808
|
+
for anIndex in aRow:
|
|
809
|
+
row.append(tVertices[anIndex-1])
|
|
810
|
+
result.append(row)
|
|
811
|
+
|
|
812
|
+
resultWires = []
|
|
813
|
+
for i in range(len(result)):
|
|
814
|
+
c = result[i]
|
|
815
|
+
resultEdges = []
|
|
816
|
+
for j in range(len(c)-1):
|
|
817
|
+
v1 = c[j]
|
|
818
|
+
v2 = c[j+1]
|
|
819
|
+
e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
|
|
820
|
+
resultEdges.append(e)
|
|
821
|
+
e = topologic.Edge.ByStartVertexEndVertex(c[len(c)-1], c[0])
|
|
822
|
+
resultEdges.append(e)
|
|
823
|
+
resultWire = topologic.Wire.ByEdges(resultEdges)
|
|
824
|
+
resultWires.append(resultWire)
|
|
825
|
+
return resultWires
|
|
826
|
+
|
|
827
|
+
@staticmethod
|
|
828
|
+
def Edges(wire: topologic.Wire) -> list:
|
|
829
|
+
"""
|
|
830
|
+
Returns the edges of the input wire.
|
|
831
|
+
|
|
832
|
+
Parameters
|
|
833
|
+
----------
|
|
834
|
+
wire : topologic.Wire
|
|
835
|
+
The input wire.
|
|
836
|
+
|
|
837
|
+
Returns
|
|
838
|
+
-------
|
|
839
|
+
list
|
|
840
|
+
The list of edges.
|
|
841
|
+
|
|
842
|
+
"""
|
|
843
|
+
if not isinstance(wire, topologic.Wire):
|
|
844
|
+
return None
|
|
845
|
+
edges = []
|
|
846
|
+
_ = wire.Edges(None, edges)
|
|
847
|
+
return edges
|
|
848
|
+
|
|
849
|
+
@staticmethod
|
|
850
|
+
def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0,0,1], placement: str = "center") -> topologic.Wire:
|
|
851
|
+
"""
|
|
852
|
+
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
|
|
853
|
+
|
|
854
|
+
Parameters
|
|
855
|
+
----------
|
|
856
|
+
origin : topologic.Vertex , optional
|
|
857
|
+
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).
|
|
858
|
+
radius : float , optional
|
|
859
|
+
The radius of the hexagon determining the size of the tile. The default is 0.5.
|
|
860
|
+
direction : list , optional
|
|
861
|
+
The vector representing the up direction of the ellipse. The default is [0,0,1].
|
|
862
|
+
placement : str , optional
|
|
863
|
+
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".
|
|
864
|
+
|
|
865
|
+
"""
|
|
866
|
+
from topologicpy.Vertex import Vertex
|
|
867
|
+
from topologicpy.Topology import Topology
|
|
868
|
+
import math
|
|
869
|
+
def cos(angle):
|
|
870
|
+
return math.cos(math.radians(angle))
|
|
871
|
+
def sin(angle):
|
|
872
|
+
return math.sin(math.radians(angle))
|
|
873
|
+
if not origin:
|
|
874
|
+
origin = Vertex.ByCoordinates(0,0,0)
|
|
875
|
+
d = cos(30)*radius
|
|
876
|
+
v1 = Vertex.ByCoordinates(0,0,0)
|
|
877
|
+
v2 = Vertex.ByCoordinates(cos(30)*d, sin(30)*d, 0)
|
|
878
|
+
v3 = Vertex.ByCoordinates(radius, 0)
|
|
879
|
+
v4 = Vertex.ByCoordinates(2*radius, 0)
|
|
880
|
+
v5 = Vertex.ByCoordinates(2*radius+cos(60)*radius*0.5, sin(30)*d, 0)
|
|
881
|
+
v6 = Vertex.ByCoordinates(1.5*radius, d)
|
|
882
|
+
v7 = Vertex.ByCoordinates(1.5*radius, 2*d)
|
|
883
|
+
v8 = Vertex.ByCoordinates(radius, 2*d)
|
|
884
|
+
v9 = Vertex.ByCoordinates(radius-cos(60)*0.5*radius, 2*d+sin(60)*0.5*radius)
|
|
885
|
+
v10 = Vertex.ByCoordinates(0, 2*d)
|
|
886
|
+
v11 = Vertex.ByCoordinates(0, d)
|
|
887
|
+
v12 = Vertex.ByCoordinates(-radius*0.5, d)
|
|
888
|
+
v13 = Vertex.ByCoordinates(-cos(30)*d, sin(30)*d, 0)
|
|
889
|
+
einstein = Wire.ByVertices([v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13], close=True)
|
|
890
|
+
|
|
891
|
+
if placement.lower() == "lowerleft":
|
|
892
|
+
einstein = Topology.Translate(einstein, radius, d, 0)
|
|
893
|
+
dx = Vertex.X(origin)
|
|
894
|
+
dy = Vertex.Y(origin)
|
|
895
|
+
dz = Vertex.Z(origin)
|
|
896
|
+
einstein = Topology.Translate(einstein, dx, dy, dz)
|
|
897
|
+
if direction != [0,0,1]:
|
|
898
|
+
einstein = Topology.Orient(einstein, origin=origin, dirA=[0,0,1], dirB=direction)
|
|
899
|
+
return einstein
|
|
900
|
+
|
|
901
|
+
@staticmethod
|
|
902
|
+
def Ellipse(origin: topologic.Vertex = None, inputMode: int = 1, width: float = 2.0, length: float = 1.0, focalLength: float = 0.866025, eccentricity: float = 0.866025, majorAxisLength: float = 1.0, minorAxisLength: float = 0.5, sides: float = 32, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
|
|
903
|
+
"""
|
|
904
|
+
Creates an ellipse and returns all its geometry and parameters.
|
|
905
|
+
|
|
906
|
+
Parameters
|
|
907
|
+
----------
|
|
908
|
+
origin : topologic.Vertex , optional
|
|
909
|
+
The location of the origin of the ellipse. The default is None which results in the ellipse being placed at (0,0,0).
|
|
910
|
+
inputMode : int , optional
|
|
911
|
+
The method by wich the ellipse is defined. The default is 1.
|
|
912
|
+
Based on the inputMode value, only the following inputs will be considered. The options are:
|
|
913
|
+
1. Width and Length (considered inputs: width, length)
|
|
914
|
+
2. Focal Length and Eccentricity (considered inputs: focalLength, eccentricity)
|
|
915
|
+
3. Focal Length and Minor Axis Length (considered inputs: focalLength, minorAxisLength)
|
|
916
|
+
4. Major Axis Length and Minor Axis Length (considered input: majorAxisLength, minorAxisLength)
|
|
917
|
+
width : float , optional
|
|
918
|
+
The width of the ellipse. The default is 2.0. This is considered if the inputMode is 1.
|
|
919
|
+
length : float , optional
|
|
920
|
+
The length of the ellipse. The default is 1.0. This is considered if the inputMode is 1.
|
|
921
|
+
focalLength : float , optional
|
|
922
|
+
The focal length of the ellipse. The default is 0.866025. This is considered if the inputMode is 2 or 3.
|
|
923
|
+
eccentricity : float , optional
|
|
924
|
+
The eccentricity of the ellipse. The default is 0.866025. This is considered if the inputMode is 2.
|
|
925
|
+
majorAxisLength : float , optional
|
|
926
|
+
The length of the major axis of the ellipse. The default is 1.0. This is considered if the inputMode is 4.
|
|
927
|
+
minorAxisLength : float , optional
|
|
928
|
+
The length of the minor axis of the ellipse. The default is 0.5. This is considered if the inputMode is 3 or 4.
|
|
929
|
+
sides : int , optional
|
|
930
|
+
The number of sides of the ellipse. The default is 32.
|
|
931
|
+
fromAngle : float , optional
|
|
932
|
+
The angle in degrees from which to start creating the arc of the ellipse. The default is 0.
|
|
933
|
+
toAngle : float , optional
|
|
934
|
+
The angle in degrees at which to end creating the arc of the ellipse. The default is 360.
|
|
935
|
+
close : bool , optional
|
|
936
|
+
If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
|
|
937
|
+
direction : list , optional
|
|
938
|
+
The vector representing the up direction of the ellipse. The default is [0,0,1].
|
|
939
|
+
placement : str , optional
|
|
940
|
+
The description of the placement of the origin of the ellipse. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
941
|
+
tolerance : float , optional
|
|
942
|
+
The desired tolerance. The default is 0.0001.
|
|
943
|
+
|
|
944
|
+
Returns
|
|
945
|
+
-------
|
|
946
|
+
topologic.Wire
|
|
947
|
+
The created ellipse
|
|
948
|
+
|
|
949
|
+
"""
|
|
950
|
+
ellipseAll = Wire.EllipseAll(origin=origin, inputMode=inputMode, width=width, length=length, focalLength=focalLength, eccentricity=eccentricity, majorAxisLength=majorAxisLength, minorAxisLength=minorAxisLength, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=close, direction=direction, placement=placement, tolerance=tolerance)
|
|
951
|
+
return ellipseAll["ellipse"]
|
|
952
|
+
|
|
953
|
+
@staticmethod
|
|
954
|
+
def EllipseAll(origin: topologic.Vertex = None, inputMode: int = 1, width: float = 2.0, length: float = 1.0, focalLength: float = 0.866025, eccentricity: float = 0.866025, majorAxisLength: float = 1.0, minorAxisLength: float = 0.5, sides: int = 32, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0,0,1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Wire:
|
|
955
|
+
"""
|
|
956
|
+
Creates an ellipse and returns all its geometry and parameters.
|
|
957
|
+
|
|
958
|
+
Parameters
|
|
959
|
+
----------
|
|
960
|
+
origin : topologic.Vertex , optional
|
|
961
|
+
The location of the origin of the ellipse. The default is None which results in the ellipse being placed at (0,0,0).
|
|
962
|
+
inputMode : int , optional
|
|
963
|
+
The method by wich the ellipse is defined. The default is 1.
|
|
964
|
+
Based on the inputMode value, only the following inputs will be considered. The options are:
|
|
965
|
+
1. Width and Length (considered inputs: width, length)
|
|
966
|
+
2. Focal Length and Eccentricity (considered inputs: focalLength, eccentricity)
|
|
967
|
+
3. Focal Length and Minor Axis Length (considered inputs: focalLength, minorAxisLength)
|
|
968
|
+
4. Major Axis Length and Minor Axis Length (considered input: majorAxisLength, minorAxisLength)
|
|
969
|
+
width : float , optional
|
|
970
|
+
The width of the ellipse. The default is 2.0. This is considered if the inputMode is 1.
|
|
971
|
+
length : float , optional
|
|
972
|
+
The length of the ellipse. The default is 1.0. This is considered if the inputMode is 1.
|
|
973
|
+
focalLength : float , optional
|
|
974
|
+
The focal length of the ellipse. The default is 0.866025. This is considered if the inputMode is 2 or 3.
|
|
975
|
+
eccentricity : float , optional
|
|
976
|
+
The eccentricity of the ellipse. The default is 0.866025. This is considered if the inputMode is 2.
|
|
977
|
+
majorAxisLength : float , optional
|
|
978
|
+
The length of the major axis of the ellipse. The default is 1.0. This is considered if the inputMode is 4.
|
|
979
|
+
minorAxisLength : float , optional
|
|
980
|
+
The length of the minor axis of the ellipse. The default is 0.5. This is considered if the inputMode is 3 or 4.
|
|
981
|
+
sides : int , optional
|
|
982
|
+
The number of sides of the ellipse. The default is 32.
|
|
983
|
+
fromAngle : float , optional
|
|
984
|
+
The angle in degrees from which to start creating the arc of the ellipse. The default is 0.
|
|
985
|
+
toAngle : float , optional
|
|
986
|
+
The angle in degrees at which to end creating the arc of the ellipse. The default is 360.
|
|
987
|
+
close : bool , optional
|
|
988
|
+
If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
|
|
989
|
+
direction : list , optional
|
|
990
|
+
The vector representing the up direction of the ellipse. The default is [0,0,1].
|
|
991
|
+
placement : str , optional
|
|
992
|
+
The description of the placement of the origin of the ellipse. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
|
993
|
+
tolerance : float , optional
|
|
994
|
+
The desired tolerance. The default is 0.0001.
|
|
995
|
+
|
|
996
|
+
Returns
|
|
997
|
+
-------
|
|
998
|
+
dictionary
|
|
999
|
+
A dictionary with the following keys and values:
|
|
1000
|
+
1. "ellipse" : The ellipse (topologic.Wire)
|
|
1001
|
+
2. "foci" : The two focal points (topologic.Cluster containing two vertices)
|
|
1002
|
+
3. "a" : The major axis length
|
|
1003
|
+
4. "b" : The minor axis length
|
|
1004
|
+
5. "c" : The focal length
|
|
1005
|
+
6. "e" : The eccentricity
|
|
1006
|
+
7. "width" : The width
|
|
1007
|
+
8. "length" : The length
|
|
1008
|
+
|
|
1009
|
+
"""
|
|
1010
|
+
if not origin:
|
|
1011
|
+
origin = topologic.Vertex.ByCoordinates(0,0,0)
|
|
1012
|
+
if not isinstance(origin, topologic.Vertex):
|
|
1013
|
+
return None
|
|
1014
|
+
if inputMode not in [1,2,3,4]:
|
|
1015
|
+
return None
|
|
1016
|
+
if placement.lower() not in ["center", "lowerleft"]:
|
|
1017
|
+
return None
|
|
1018
|
+
if (abs(direction[0]) + abs(direction[1]) + abs(direction[2])) < tolerance:
|
|
1019
|
+
return None
|
|
1020
|
+
width = abs(width)
|
|
1021
|
+
length = abs(length)
|
|
1022
|
+
focalLength= abs(focalLength)
|
|
1023
|
+
eccentricity=abs(eccentricity)
|
|
1024
|
+
majorAxisLength=abs(majorAxisLength)
|
|
1025
|
+
minorAxisLength=abs(minorAxisLength)
|
|
1026
|
+
sides = abs(sides)
|
|
1027
|
+
if width < tolerance or length < tolerance or focalLength < tolerance or eccentricity < tolerance or majorAxisLength < tolerance or minorAxisLength < tolerance or sides < 3:
|
|
1028
|
+
return None
|
|
1029
|
+
if inputMode == 1:
|
|
1030
|
+
w = width
|
|
1031
|
+
l = length
|
|
1032
|
+
a = width/2
|
|
1033
|
+
b = length/2
|
|
1034
|
+
c = math.sqrt(abs(b**2 - a**2))
|
|
1035
|
+
e = c/a
|
|
1036
|
+
elif inputMode == 2:
|
|
1037
|
+
c = focalLength
|
|
1038
|
+
e = eccentricity
|
|
1039
|
+
a = c/e
|
|
1040
|
+
b = math.sqrt(abs(a**2 - c**2))
|
|
1041
|
+
w = a*2
|
|
1042
|
+
l = b*2
|
|
1043
|
+
elif inputMode == 3:
|
|
1044
|
+
c = focalLength
|
|
1045
|
+
b = minorAxisLength
|
|
1046
|
+
a = math.sqrt(abs(b**2 + c**2))
|
|
1047
|
+
e = c/a
|
|
1048
|
+
w = a*2
|
|
1049
|
+
l = b*2
|
|
1050
|
+
elif inputMode == 4:
|
|
1051
|
+
a = majorAxisLength
|
|
1052
|
+
b = minorAxisLength
|
|
1053
|
+
c = math.sqrt(abs(b**2 - a**2))
|
|
1054
|
+
e = c/a
|
|
1055
|
+
w = a*2
|
|
1056
|
+
l = b*2
|
|
1057
|
+
else:
|
|
1058
|
+
return None
|
|
1059
|
+
baseV = []
|
|
1060
|
+
xList = []
|
|
1061
|
+
yList = []
|
|
1062
|
+
|
|
1063
|
+
if toAngle < fromAngle:
|
|
1064
|
+
toAngle += 360
|
|
1065
|
+
if abs(toAngle - fromAngle) < tolerance:
|
|
1066
|
+
return None
|
|
1067
|
+
|
|
1068
|
+
angleRange = toAngle - fromAngle
|
|
1069
|
+
fromAngle = math.radians(fromAngle)
|
|
1070
|
+
toAngle = math.radians(toAngle)
|
|
1071
|
+
sides = int(math.floor(sides))
|
|
1072
|
+
for i in range(sides+1):
|
|
1073
|
+
angle = fromAngle + math.radians(angleRange/sides)*i
|
|
1074
|
+
x = math.sin(angle)*a + origin.X()
|
|
1075
|
+
y = math.cos(angle)*b + origin.Y()
|
|
1076
|
+
z = origin.Z()
|
|
1077
|
+
xList.append(x)
|
|
1078
|
+
yList.append(y)
|
|
1079
|
+
baseV.append(topologic.Vertex.ByCoordinates(x,y,z))
|
|
1080
|
+
|
|
1081
|
+
ellipse = Wire.ByVertices(baseV[::-1], close) #reversing the list so that the normal points up in Blender
|
|
1082
|
+
|
|
1083
|
+
if placement.lower() == "lowerleft":
|
|
1084
|
+
ellipse = topologic.TopologyUtility.Translate(ellipse, a, b, 0)
|
|
1085
|
+
x1 = origin.X()
|
|
1086
|
+
y1 = origin.Y()
|
|
1087
|
+
z1 = origin.Z()
|
|
1088
|
+
x2 = origin.X() + direction[0]
|
|
1089
|
+
y2 = origin.Y() + direction[1]
|
|
1090
|
+
z2 = origin.Z() + direction[2]
|
|
1091
|
+
dx = x2 - x1
|
|
1092
|
+
dy = y2 - y1
|
|
1093
|
+
dz = z2 - z1
|
|
1094
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
1095
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
1096
|
+
if dist < 0.0001:
|
|
1097
|
+
theta = 0
|
|
1098
|
+
else:
|
|
1099
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
1100
|
+
ellipse = topologic.TopologyUtility.Rotate(ellipse, origin, 0, 1, 0, theta)
|
|
1101
|
+
ellipse = topologic.TopologyUtility.Rotate(ellipse, origin, 0, 0, 1, phi)
|
|
1102
|
+
|
|
1103
|
+
# Create a Cluster of the two foci
|
|
1104
|
+
v1 = topologic.Vertex.ByCoordinates(c+origin.X(), 0+origin.Y(),0)
|
|
1105
|
+
v2 = topologic.Vertex.ByCoordinates(-c+origin.X(), 0+origin.Y(),0)
|
|
1106
|
+
foci = topologic.Cluster.ByTopologies([v1, v2])
|
|
1107
|
+
if placement.lower() == "lowerleft":
|
|
1108
|
+
foci = topologic.TopologyUtility.Translate(foci, a, b, 0)
|
|
1109
|
+
foci = topologic.TopologyUtility.Rotate(foci, origin, 0, 1, 0, theta)
|
|
1110
|
+
foci = topologic.TopologyUtility.Rotate(foci, origin, 0, 0, 1, phi)
|
|
1111
|
+
d = {}
|
|
1112
|
+
d['ellipse'] = ellipse
|
|
1113
|
+
d['foci'] = foci
|
|
1114
|
+
d['a'] = a
|
|
1115
|
+
d['b'] = b
|
|
1116
|
+
d['c'] = c
|
|
1117
|
+
d['e'] = e
|
|
1118
|
+
d['w'] = w
|
|
1119
|
+
d['l'] = l
|
|
1120
|
+
return d
|
|
1121
|
+
|
|
1122
|
+
@staticmethod
|
|
1123
|
+
def Flatten(wire: topologic.Wire, oldLocation: topologic.Vertex =None, newLocation: topologic.Vertex = None, direction: list = None):
|
|
1124
|
+
"""
|
|
1125
|
+
Flattens the input wire such that its center of mass is located at the origin and the specified direction is pointed in the positive Z axis.
|
|
1126
|
+
|
|
1127
|
+
Parameters
|
|
1128
|
+
----------
|
|
1129
|
+
wire : topologic.Wire
|
|
1130
|
+
The input wire.
|
|
1131
|
+
oldLocation : topologic.Vertex , optional
|
|
1132
|
+
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.
|
|
1133
|
+
newLocation : topologic.Vertex , optional
|
|
1134
|
+
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.
|
|
1135
|
+
direction : list , optional
|
|
1136
|
+
The direction, expressed as a list of [X,Y,Z] that signifies the direction of the wire. If set to None, the positive ZAxis direction is considered the direction of the wire. The deafult is None.
|
|
1137
|
+
|
|
1138
|
+
Returns
|
|
1139
|
+
-------
|
|
1140
|
+
topologic.Wire
|
|
1141
|
+
The flattened wire.
|
|
1142
|
+
|
|
1143
|
+
"""
|
|
1144
|
+
from topologicpy.Vertex import Vertex
|
|
1145
|
+
from topologicpy.Edge import Edge
|
|
1146
|
+
from topologicpy.Cluster import Cluster
|
|
1147
|
+
from topologicpy.Topology import Topology
|
|
1148
|
+
from topologicpy.Dictionary import Dictionary
|
|
1149
|
+
from topologicpy.Vector import Vector
|
|
1150
|
+
if not isinstance(wire, topologic.Wire):
|
|
1151
|
+
return None
|
|
1152
|
+
if direction == None:
|
|
1153
|
+
direction = Vector.ZAxis()
|
|
1154
|
+
if not isinstance(oldLocation, topologic.Vertex):
|
|
1155
|
+
oldLocation = Topology.CenterOfMass(wire)
|
|
1156
|
+
if not isinstance(newLocation, topologic.Vertex):
|
|
1157
|
+
newLocation = Vertex.ByCoordinates(0,0,0)
|
|
1158
|
+
cm = oldLocation
|
|
1159
|
+
world_origin = newLocation
|
|
1160
|
+
|
|
1161
|
+
x1 = Vertex.X(cm)
|
|
1162
|
+
y1 = Vertex.Y(cm)
|
|
1163
|
+
z1 = Vertex.Z(cm)
|
|
1164
|
+
x2 = Vertex.X(cm) + direction[0]
|
|
1165
|
+
y2 = Vertex.Y(cm) + direction[1]
|
|
1166
|
+
z2 = Vertex.Z(cm) + direction[2]
|
|
1167
|
+
dx = x2 - x1
|
|
1168
|
+
dy = y2 - y1
|
|
1169
|
+
dz = z2 - z1
|
|
1170
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
1171
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
1172
|
+
if dist < 0.0001:
|
|
1173
|
+
theta = 0
|
|
1174
|
+
else:
|
|
1175
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
1176
|
+
flatWire = Topology.Translate(wire, -cm.X(), -cm.Y(), -cm.Z())
|
|
1177
|
+
flatWire = Topology.Rotate(flatWire, world_origin, 0, 0, 1, -phi)
|
|
1178
|
+
flatWire = Topology.Rotate(flatWire, world_origin, 0, 1, 0, -theta)
|
|
1179
|
+
# Ensure flatness. Force Z to be zero
|
|
1180
|
+
edges = Wire.Edges(flatWire)
|
|
1181
|
+
flatEdges = []
|
|
1182
|
+
for edge in edges:
|
|
1183
|
+
sv = Edge.StartVertex(edge)
|
|
1184
|
+
ev = Edge.EndVertex(edge)
|
|
1185
|
+
sv1 = Vertex.ByCoordinates(Vertex.X(sv), Vertex.Y(sv), 0)
|
|
1186
|
+
ev1 = Vertex.ByCoordinates(Vertex.X(ev), Vertex.Y(ev), 0)
|
|
1187
|
+
e1 = Edge.ByVertices([sv1, ev1])
|
|
1188
|
+
flatEdges.append(e1)
|
|
1189
|
+
flatWire = Topology.SelfMerge(Cluster.ByTopologies(flatEdges))
|
|
1190
|
+
dictionary = Dictionary.ByKeysValues(["xTran", "yTran", "zTran", "phi", "theta"], [cm.X(), cm.Y(), cm.Z(), phi, theta])
|
|
1191
|
+
flatWire = Topology.SetDictionary(flatWire, dictionary)
|
|
1192
|
+
return flatWire
|
|
1193
|
+
|
|
1194
|
+
@staticmethod
|
|
1195
|
+
def Interpolate(wires: list, n: int = 5, outputType: str = "default", replication: str = "default") -> topologic.Topology:
|
|
1196
|
+
"""
|
|
1197
|
+
Creates *n* number of wires that interpolate between wireA and wireB.
|
|
1198
|
+
|
|
1199
|
+
Parameters
|
|
1200
|
+
----------
|
|
1201
|
+
wireA : topologic.Wire
|
|
1202
|
+
The first input wire.
|
|
1203
|
+
wireB : topologic.Wire
|
|
1204
|
+
The second input wire.
|
|
1205
|
+
n : int , optional
|
|
1206
|
+
The number of intermediate wires to create. The default is 5.
|
|
1207
|
+
outputType : str , optional
|
|
1208
|
+
The desired type of output. The options are case insensitive. The default is "contour". The options are:
|
|
1209
|
+
- "Default" or "Contours" (wires are not connected)
|
|
1210
|
+
- "Raster or "Zigzag" or "Toolpath" (the wire ends are connected to create a continous path)
|
|
1211
|
+
- "Grid" (the wire ends are connected to create a grid).
|
|
1212
|
+
replication : str , optiona;
|
|
1213
|
+
The desired type of replication for wires with different number of vertices. It is case insensitive. The default is "default". The options are:
|
|
1214
|
+
- "Default" or "Repeat" which repeats the last vertex of the wire with the least number of vertices
|
|
1215
|
+
- "Nearest" which maps the vertices of one wire to the nearest vertex of the next wire creating a list of equal number of vertices.
|
|
1216
|
+
Returns
|
|
1217
|
+
-------
|
|
1218
|
+
toplogic.Topology
|
|
1219
|
+
The created interpolated wires as well as the input wires. The return type can be a topologic.Cluster or a topologic.Wire based on options.
|
|
1220
|
+
|
|
1221
|
+
"""
|
|
1222
|
+
|
|
1223
|
+
from topologicpy.Vertex import Vertex
|
|
1224
|
+
from topologicpy.Edge import Edge
|
|
1225
|
+
from topologicpy.Face import Face
|
|
1226
|
+
from topologicpy.Cluster import Cluster
|
|
1227
|
+
from topologicpy.Helper import Helper
|
|
1228
|
+
|
|
1229
|
+
outputType = outputType.lower()
|
|
1230
|
+
if outputType not in ["default", "contours", "raster", "zigzag", "toolpath", "grid"]:
|
|
1231
|
+
return None
|
|
1232
|
+
if outputType == "default" or outputType == "contours":
|
|
1233
|
+
outputType = "contours"
|
|
1234
|
+
if outputType == "raster" or outputType == "zigzag" or outputType == "toolpath":
|
|
1235
|
+
outputType = "zigzag"
|
|
1236
|
+
|
|
1237
|
+
replication = replication.lower()
|
|
1238
|
+
if replication not in ["default", "nearest", "repeat"]:
|
|
1239
|
+
return None
|
|
1240
|
+
|
|
1241
|
+
def nearestVertex(v, vertices):
|
|
1242
|
+
distances = [Vertex.Distance(v, vertex) for vertex in vertices]
|
|
1243
|
+
return vertices[distances.index(sorted(distances)[0])]
|
|
1244
|
+
|
|
1245
|
+
def replicate(vertices, replication="default"):
|
|
1246
|
+
vertices = Helper.Repeat(vertices)
|
|
1247
|
+
finalList = vertices
|
|
1248
|
+
if replication == "nearest":
|
|
1249
|
+
finalList = [vertices[0]]
|
|
1250
|
+
for i in range(len(vertices)-1):
|
|
1251
|
+
loopA = vertices[i]
|
|
1252
|
+
loopB = vertices[i+1]
|
|
1253
|
+
nearestVertices = []
|
|
1254
|
+
for j in range(len(loopA)):
|
|
1255
|
+
#clusB = Cluster.ByTopologies(loopB)
|
|
1256
|
+
#nv = Vertex.NearestVertex(loopA[j], clusB, useKDTree=False)
|
|
1257
|
+
nv = nearestVertex(loopA[j], loopB)
|
|
1258
|
+
nearestVertices.append(nv)
|
|
1259
|
+
finalList.append(nearestVertices)
|
|
1260
|
+
return finalList
|
|
1261
|
+
|
|
1262
|
+
def process(verticesA, verticesB, n=5, outputType="contours", replication="repeat"):
|
|
1263
|
+
#if outputType == "zigzag" and Wire.IsClosed(wireA):
|
|
1264
|
+
#verticesA.append(verticesA[0])
|
|
1265
|
+
#verticesA, verticesB = replicate(verticesA=verticesA, verticesB=verticesB, replication=replication)
|
|
1266
|
+
|
|
1267
|
+
contours = [verticesA]
|
|
1268
|
+
for i in range(1, n+1):
|
|
1269
|
+
u = float(i)/float(n+1)
|
|
1270
|
+
temp_vertices = []
|
|
1271
|
+
for j in range(len(verticesA)):
|
|
1272
|
+
temp_v = Edge.VertexByParameter(Edge.ByVertices([verticesA[j], verticesB[j]]), u)
|
|
1273
|
+
temp_vertices.append(temp_v)
|
|
1274
|
+
contours.append(temp_vertices)
|
|
1275
|
+
return contours
|
|
1276
|
+
|
|
1277
|
+
if len(wires) < 2:
|
|
1278
|
+
return None
|
|
1279
|
+
|
|
1280
|
+
vertices = []
|
|
1281
|
+
for wire in wires:
|
|
1282
|
+
vertices.append(Topology.SubTopologies(wire, subTopologyType="vertex"))
|
|
1283
|
+
vertices = replicate(vertices, replication=replication)
|
|
1284
|
+
contours = []
|
|
1285
|
+
|
|
1286
|
+
finalWires = []
|
|
1287
|
+
for i in range(len(vertices)-1):
|
|
1288
|
+
verticesA = vertices[i]
|
|
1289
|
+
verticesB = vertices[i+1]
|
|
1290
|
+
contour = process(verticesA=verticesA, verticesB=verticesB, n=n, outputType=outputType, replication=replication)
|
|
1291
|
+
contours += contour
|
|
1292
|
+
for c in contour:
|
|
1293
|
+
finalWires.append(Wire.ByVertices(c, Wire.IsClosed(wires[i])))
|
|
1294
|
+
|
|
1295
|
+
contours.append(vertices[-1])
|
|
1296
|
+
finalWires.append(wires[-1])
|
|
1297
|
+
ridges = []
|
|
1298
|
+
if outputType == "grid" or outputType == "zigzag":
|
|
1299
|
+
for i in range(len(contours)-1):
|
|
1300
|
+
verticesA = contours[i]
|
|
1301
|
+
verticesB = contours[i+1]
|
|
1302
|
+
if outputType == "grid":
|
|
1303
|
+
for j in range(len(verticesA)):
|
|
1304
|
+
ridges.append(Edge.ByVertices([verticesA[j], verticesB[j]]))
|
|
1305
|
+
elif outputType == "zigzag":
|
|
1306
|
+
if i%2 == 0:
|
|
1307
|
+
sv = verticesA[-1]
|
|
1308
|
+
ev = verticesB[-1]
|
|
1309
|
+
ridges.append(Edge.ByVertices([sv, ev]))
|
|
1310
|
+
else:
|
|
1311
|
+
sv = verticesA[0]
|
|
1312
|
+
ev = verticesB[0]
|
|
1313
|
+
ridges.append(Edge.ByVertices([sv, ev]))
|
|
1314
|
+
|
|
1315
|
+
return Topology.SelfMerge(Cluster.ByTopologies(finalWires+ridges))
|
|
1316
|
+
|
|
1317
|
+
@staticmethod
|
|
1318
|
+
def Invert(wire: topologic.Wire) -> topologic.Wire:
|
|
1319
|
+
"""
|
|
1320
|
+
Creates a wire that is an inverse (mirror) of the input wire.
|
|
1321
|
+
|
|
1322
|
+
Parameters
|
|
1323
|
+
----------
|
|
1324
|
+
wire : topologic.Wire
|
|
1325
|
+
The input wire.
|
|
1326
|
+
|
|
1327
|
+
Returns
|
|
1328
|
+
-------
|
|
1329
|
+
topologic.Wire
|
|
1330
|
+
The inverted wire.
|
|
1331
|
+
|
|
1332
|
+
"""
|
|
1333
|
+
if not isinstance(wire, topologic.Wire):
|
|
1334
|
+
return None
|
|
1335
|
+
vertices = Wire.Vertices(wire)
|
|
1336
|
+
reversed_vertices = vertices[::-1]
|
|
1337
|
+
return Wire.ByVertices(reversed_vertices)
|
|
1338
|
+
|
|
1339
|
+
@staticmethod
|
|
1340
|
+
def IsClosed(wire: topologic.Wire) -> bool:
|
|
1341
|
+
"""
|
|
1342
|
+
Returns True if the input wire is closed. Returns False otherwise.
|
|
1343
|
+
|
|
1344
|
+
Parameters
|
|
1345
|
+
----------
|
|
1346
|
+
wire : topologic.Wire
|
|
1347
|
+
The input wire.
|
|
1348
|
+
|
|
1349
|
+
Returns
|
|
1350
|
+
-------
|
|
1351
|
+
bool
|
|
1352
|
+
True if the input wire is closed. False otherwise.
|
|
1353
|
+
|
|
1354
|
+
"""
|
|
1355
|
+
status = None
|
|
1356
|
+
if wire:
|
|
1357
|
+
if isinstance(wire, topologic.Wire):
|
|
1358
|
+
status = wire.IsClosed()
|
|
1359
|
+
return status
|
|
1360
|
+
|
|
1361
|
+
@staticmethod
|
|
1362
|
+
def IsInside(wire: topologic.Wire, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
|
|
1363
|
+
"""
|
|
1364
|
+
Returns True if the input vertex is inside the input wire. Returns False otherwise.
|
|
1365
|
+
|
|
1366
|
+
Parameters
|
|
1367
|
+
----------
|
|
1368
|
+
wire : topologic.Wire
|
|
1369
|
+
The input wire.
|
|
1370
|
+
vertex : topologic.Vertex
|
|
1371
|
+
The input Vertex.
|
|
1372
|
+
tolerance : float , optional
|
|
1373
|
+
The desired tolerance. The default is 0.0001.
|
|
1374
|
+
|
|
1375
|
+
Returns
|
|
1376
|
+
-------
|
|
1377
|
+
bool
|
|
1378
|
+
True if the input vertex is inside the input wire. False otherwise.
|
|
1379
|
+
|
|
1380
|
+
"""
|
|
1381
|
+
from topologicpy.Vertex import Vertex
|
|
1382
|
+
from topologicpy.Edge import Edge
|
|
1383
|
+
|
|
1384
|
+
if not isinstance(wire, topologic.Wire):
|
|
1385
|
+
return None
|
|
1386
|
+
if not isinstance(vertex, topologic.Vertex):
|
|
1387
|
+
return None
|
|
1388
|
+
is_inside = False
|
|
1389
|
+
edges = Wire.Edges(wire)
|
|
1390
|
+
for edge in edges:
|
|
1391
|
+
if (Vertex.Distance(vertex, edge) <= tolerance):
|
|
1392
|
+
return True
|
|
1393
|
+
return False
|
|
1394
|
+
|
|
1395
|
+
@staticmethod
|
|
1396
|
+
def Isovist(wire: topologic.Wire, viewPoint: topologic.Vertex, obstaclesCluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
|
1397
|
+
"""
|
|
1398
|
+
Returns a list of faces representing the isovist projection from the input viewpoint.
|
|
1399
|
+
|
|
1400
|
+
Parameters
|
|
1401
|
+
----------
|
|
1402
|
+
wire : topologic.Wire
|
|
1403
|
+
The wire representing the external boundary (border) of the isovist.
|
|
1404
|
+
viewPoint : topologic.Vertex
|
|
1405
|
+
The vertex representing the location of the viewpoint of the isovist.
|
|
1406
|
+
obstaclesCluster : topologic.Cluster
|
|
1407
|
+
A cluster of wires representing the obstacles within the externalBoundary.
|
|
1408
|
+
|
|
1409
|
+
Returns
|
|
1410
|
+
-------
|
|
1411
|
+
list
|
|
1412
|
+
A list of faces representing the isovist projection from the input viewpoint.
|
|
1413
|
+
|
|
1414
|
+
"""
|
|
1415
|
+
|
|
1416
|
+
def vertexPartofFace(vertex, face, tolerance):
|
|
1417
|
+
vertices = []
|
|
1418
|
+
_ = face.Vertices(None, vertices)
|
|
1419
|
+
for v in vertices:
|
|
1420
|
+
if topologic.VertexUtility.Distance(vertex, v) < tolerance:
|
|
1421
|
+
return True
|
|
1422
|
+
return False
|
|
1423
|
+
|
|
1424
|
+
internalBoundaries = []
|
|
1425
|
+
_ = obstaclesCluster.Wires(None, internalBoundaries)
|
|
1426
|
+
internalVertices = []
|
|
1427
|
+
_ = obstaclesCluster.Vertices(None, internalVertices)
|
|
1428
|
+
# 1. Create a Face with external and internal boundaries
|
|
1429
|
+
face = topologic.Face.ByExternalInternalBoundaries(wire, internalBoundaries, False)
|
|
1430
|
+
# 2. Draw Rays from viewpoint through each Vertex of the obstacles extending to the External Boundary
|
|
1431
|
+
# 2.1 Get the Edges and Vertices of the External Boundary
|
|
1432
|
+
exBoundaryEdges = []
|
|
1433
|
+
_ = wire.Edges(None, exBoundaryEdges)
|
|
1434
|
+
exBoundaryVertices = []
|
|
1435
|
+
_ = wire.Vertices(None, exBoundaryVertices)
|
|
1436
|
+
testTopologies = exBoundaryEdges+exBoundaryVertices
|
|
1437
|
+
# 1.2 Find the maximum distance from the viewpoint to the edges and vertices of the external boundary
|
|
1438
|
+
distances = []
|
|
1439
|
+
for x in testTopologies:
|
|
1440
|
+
distances.append(topologic.VertexUtility.Distance(viewPoint, x))
|
|
1441
|
+
maxDistance = max(distances)*1.5
|
|
1442
|
+
# 1.3 Shoot rays and intersect with the external boundary
|
|
1443
|
+
rays = []
|
|
1444
|
+
for aVertex in (internalVertices+exBoundaryVertices):
|
|
1445
|
+
d = topologic.VertexUtility.Distance(viewPoint, aVertex)
|
|
1446
|
+
if d > tolerance:
|
|
1447
|
+
scaleFactor = maxDistance/d
|
|
1448
|
+
newV = topologic.TopologyUtility.Scale(aVertex, viewPoint, scaleFactor, scaleFactor, scaleFactor)
|
|
1449
|
+
try:
|
|
1450
|
+
ray = topologic.Edge.ByStartVertexEndVertex(viewPoint, newV)
|
|
1451
|
+
topologyC = ray.Intersect(wire, False)
|
|
1452
|
+
vertices = []
|
|
1453
|
+
_ = topologyC.Vertices(None, vertices)
|
|
1454
|
+
if topologyC:
|
|
1455
|
+
try:
|
|
1456
|
+
rays.append(topologic.Edge.ByStartVertexEndVertex(viewPoint, vertices[0]))
|
|
1457
|
+
except:
|
|
1458
|
+
pass
|
|
1459
|
+
try:
|
|
1460
|
+
rays.append(topologic.Edge.ByStartVertexEndVertex(viewPoint, aVertex))
|
|
1461
|
+
except:
|
|
1462
|
+
pass
|
|
1463
|
+
except:
|
|
1464
|
+
pass
|
|
1465
|
+
rayEdges = []
|
|
1466
|
+
for r in rays:
|
|
1467
|
+
a = r.Difference(obstaclesCluster, False)
|
|
1468
|
+
if a:
|
|
1469
|
+
edges = []
|
|
1470
|
+
_ = a.Edges(None, edges)
|
|
1471
|
+
w = None
|
|
1472
|
+
try:
|
|
1473
|
+
w = topologic.Wire.ByEdges(edges)
|
|
1474
|
+
rayEdges = rayEdges + edges
|
|
1475
|
+
except:
|
|
1476
|
+
c = topologic.Cluster.ByTopologies(edges)
|
|
1477
|
+
c = c.SelfMerge()
|
|
1478
|
+
wires = []
|
|
1479
|
+
_ = c.Wires(None, wires)
|
|
1480
|
+
if len(wires) > 0:
|
|
1481
|
+
edges = []
|
|
1482
|
+
_ = wires[0].Edges(None, edges)
|
|
1483
|
+
rayEdges = rayEdges + edges
|
|
1484
|
+
else:
|
|
1485
|
+
for e in edges:
|
|
1486
|
+
vertices = []
|
|
1487
|
+
e.Vertices(None, vertices)
|
|
1488
|
+
for v in vertices:
|
|
1489
|
+
if topologic.VertexUtility.Distance(viewPoint, v) < tolerance:
|
|
1490
|
+
rayEdges.append(e)
|
|
1491
|
+
rayCluster = topologic.Cluster.ByTopologies(rayEdges)
|
|
1492
|
+
#return rayCluster
|
|
1493
|
+
shell = face.Slice(rayCluster, False)
|
|
1494
|
+
faces = []
|
|
1495
|
+
_ = shell.Faces(None, faces)
|
|
1496
|
+
finalFaces = []
|
|
1497
|
+
for aFace in faces:
|
|
1498
|
+
if vertexPartofFace(viewPoint, aFace, 0.001):
|
|
1499
|
+
finalFaces.append(aFace)
|
|
1500
|
+
return finalFaces
|
|
1501
|
+
|
|
1502
|
+
@staticmethod
|
|
1503
|
+
def IsSimilar(wireA: topologic.Wire, wireB: topologic.Wire, angTolerance: float = 0.1, tolerance: float = 0.0001) -> bool:
|
|
1504
|
+
"""
|
|
1505
|
+
Returns True if the input wires are similar. Returns False otherwise. The wires must be closed.
|
|
1506
|
+
|
|
1507
|
+
Parameters
|
|
1508
|
+
----------
|
|
1509
|
+
wireA : topologic.Wire
|
|
1510
|
+
The first input wire.
|
|
1511
|
+
wireB : topologic.Wire
|
|
1512
|
+
The second input wire.
|
|
1513
|
+
angTolerance : float , optional
|
|
1514
|
+
The desired angular tolerance. The default is 0.1.
|
|
1515
|
+
tolerance : float , optional
|
|
1516
|
+
The desired tolerance. The default is 0.0001.
|
|
1517
|
+
|
|
1518
|
+
Returns
|
|
1519
|
+
-------
|
|
1520
|
+
bool
|
|
1521
|
+
True if the two input wires are similar. False otherwise.
|
|
1522
|
+
|
|
1523
|
+
"""
|
|
1524
|
+
|
|
1525
|
+
def isCyclicallyEquivalent(u, v, lengthTolerance, angleTolerance):
|
|
1526
|
+
n, i, j = len(u), 0, 0
|
|
1527
|
+
if n != len(v):
|
|
1528
|
+
return False
|
|
1529
|
+
while i < n and j < n:
|
|
1530
|
+
if (i % 2) == 0:
|
|
1531
|
+
tol = lengthTolerance
|
|
1532
|
+
else:
|
|
1533
|
+
tol = angleTolerance
|
|
1534
|
+
k = 1
|
|
1535
|
+
while k <= n and math.fabs(u[(i + k) % n]- v[(j + k) % n]) <= tol:
|
|
1536
|
+
k += 1
|
|
1537
|
+
if k > n:
|
|
1538
|
+
return True
|
|
1539
|
+
if math.fabs(u[(i + k) % n]- v[(j + k) % n]) > tol:
|
|
1540
|
+
i += k
|
|
1541
|
+
else:
|
|
1542
|
+
j += k
|
|
1543
|
+
return False
|
|
1544
|
+
|
|
1545
|
+
def angleBetweenEdges(e1, e2, tolerance):
|
|
1546
|
+
a = e1.EndVertex().X() - e1.StartVertex().X()
|
|
1547
|
+
b = e1.EndVertex().Y() - e1.StartVertex().Y()
|
|
1548
|
+
c = e1.EndVertex().Z() - e1.StartVertex().Z()
|
|
1549
|
+
d = topologic.VertexUtility.Distance(e1.EndVertex(), e2.StartVertex())
|
|
1550
|
+
if d <= tolerance:
|
|
1551
|
+
d = e2.StartVertex().X() - e2.EndVertex().X()
|
|
1552
|
+
e = e2.StartVertex().Y() - e2.EndVertex().Y()
|
|
1553
|
+
f = e2.StartVertex().Z() - e2.EndVertex().Z()
|
|
1554
|
+
else:
|
|
1555
|
+
d = e2.EndVertex().X() - e2.StartVertex().X()
|
|
1556
|
+
e = e2.EndVertex().Y() - e2.StartVertex().Y()
|
|
1557
|
+
f = e2.EndVertex().Z() - e2.StartVertex().Z()
|
|
1558
|
+
dotProduct = a*d + b*e + c*f
|
|
1559
|
+
modOfVector1 = math.sqrt( a*a + b*b + c*c)*math.sqrt(d*d + e*e + f*f)
|
|
1560
|
+
angle = dotProduct/modOfVector1
|
|
1561
|
+
angleInDegrees = math.degrees(math.acos(angle))
|
|
1562
|
+
return angleInDegrees
|
|
1563
|
+
|
|
1564
|
+
def getInteriorAngles(edges, tolerance):
|
|
1565
|
+
angles = []
|
|
1566
|
+
for i in range(len(edges)-1):
|
|
1567
|
+
e1 = edges[i]
|
|
1568
|
+
e2 = edges[i+1]
|
|
1569
|
+
angles.append(angleBetweenEdges(e1, e2, tolerance))
|
|
1570
|
+
return angles
|
|
1571
|
+
|
|
1572
|
+
def getRep(edges, tolerance):
|
|
1573
|
+
angles = getInteriorAngles(edges, tolerance)
|
|
1574
|
+
lengths = []
|
|
1575
|
+
for anEdge in edges:
|
|
1576
|
+
lengths.append(topologic.EdgeUtility.Length(anEdge))
|
|
1577
|
+
minLength = min(lengths)
|
|
1578
|
+
normalisedLengths = []
|
|
1579
|
+
for aLength in lengths:
|
|
1580
|
+
normalisedLengths.append(aLength/minLength)
|
|
1581
|
+
return [x for x in itertools.chain(*itertools.zip_longest(normalisedLengths, angles)) if x is not None]
|
|
1582
|
+
|
|
1583
|
+
if (wireA.IsClosed() == False):
|
|
1584
|
+
return None
|
|
1585
|
+
if (wireB.IsClosed() == False):
|
|
1586
|
+
return None
|
|
1587
|
+
edgesA = []
|
|
1588
|
+
_ = wireA.Edges(None, edgesA)
|
|
1589
|
+
edgesB = []
|
|
1590
|
+
_ = wireB.Edges(None, edgesB)
|
|
1591
|
+
if len(edgesA) != len(edgesB):
|
|
1592
|
+
return False
|
|
1593
|
+
repA = getRep(list(edgesA), tolerance)
|
|
1594
|
+
repB = getRep(list(edgesB), tolerance)
|
|
1595
|
+
if isCyclicallyEquivalent(repA, repB, tolerance, angTolerance):
|
|
1596
|
+
return True
|
|
1597
|
+
if isCyclicallyEquivalent(repA, repB[::-1], tolerance, angTolerance):
|
|
1598
|
+
return True
|
|
1599
|
+
return False
|
|
1600
|
+
|
|
1601
|
+
@staticmethod
|
|
1602
|
+
def Length(wire: topologic.Wire, mantissa: int = 4) -> float:
|
|
1603
|
+
"""
|
|
1604
|
+
Returns the length of the input wire.
|
|
1605
|
+
|
|
1606
|
+
Parameters
|
|
1607
|
+
----------
|
|
1608
|
+
wire : topologic.Wire
|
|
1609
|
+
The input wire.
|
|
1610
|
+
mantissa : int , optional
|
|
1611
|
+
The desired length of the mantissa. The default is 4.
|
|
1612
|
+
|
|
1613
|
+
Returns
|
|
1614
|
+
-------
|
|
1615
|
+
float
|
|
1616
|
+
The length of the input wire.
|
|
1617
|
+
|
|
1618
|
+
"""
|
|
1619
|
+
if not wire:
|
|
1620
|
+
return None
|
|
1621
|
+
if not isinstance(wire, topologic.Wire):
|
|
1622
|
+
return None
|
|
1623
|
+
totalLength = None
|
|
1624
|
+
try:
|
|
1625
|
+
edges = []
|
|
1626
|
+
_ = wire.Edges(None, edges)
|
|
1627
|
+
totalLength = 0
|
|
1628
|
+
for anEdge in edges:
|
|
1629
|
+
totalLength = totalLength + topologic.EdgeUtility.Length(anEdge)
|
|
1630
|
+
totalLength = round(totalLength, mantissa)
|
|
1631
|
+
except:
|
|
1632
|
+
totalLength = None
|
|
1633
|
+
return totalLength
|
|
1634
|
+
|
|
1635
|
+
@staticmethod
|
|
1636
|
+
def Planarize(wire: topologic.Wire) -> topologic.Wire:
|
|
1637
|
+
"""
|
|
1638
|
+
Returns a planarized version of the input wire.
|
|
1639
|
+
|
|
1640
|
+
Parameters
|
|
1641
|
+
----------
|
|
1642
|
+
wire : topologic.Wire
|
|
1643
|
+
The input wire.
|
|
1644
|
+
|
|
1645
|
+
Returns
|
|
1646
|
+
-------
|
|
1647
|
+
topologic.Wire
|
|
1648
|
+
The planarized wire.
|
|
1649
|
+
|
|
1650
|
+
"""
|
|
1651
|
+
from topologicpy.Vertex import Vertex
|
|
1652
|
+
from topologicpy.Face import Face
|
|
1653
|
+
from topologicpy.Topology import Topology
|
|
1654
|
+
if not isinstance(wire, topologic.Wire):
|
|
1655
|
+
return None
|
|
1656
|
+
verts = []
|
|
1657
|
+
_ = wire.Vertices(None, verts)
|
|
1658
|
+
w = Wire.ByVertices([verts[0], verts[1], verts[2]], close=True)
|
|
1659
|
+
f = topologic.Face.ByExternalBoundary(w)
|
|
1660
|
+
f = Topology.Scale(f, f.Centroid(), 500,500,500)
|
|
1661
|
+
proj_verts = []
|
|
1662
|
+
direction = Face.NormalAtParameters(f)
|
|
1663
|
+
for v in verts:
|
|
1664
|
+
v = Vertex.ByCoordinates(v.X()+direction[0]*5, v.Y()+direction[1]*5, v.Z()+direction[2]*5)
|
|
1665
|
+
proj_verts.append(Vertex.Project(v, f))
|
|
1666
|
+
return Wire.ByVertices(proj_verts, close=True)
|
|
1667
|
+
|
|
1668
|
+
@staticmethod
|
|
1669
|
+
def Project(wire: topologic.Wire, face: topologic.Face, direction: list = None, mantissa: int = 4) -> topologic.Wire:
|
|
1670
|
+
"""
|
|
1671
|
+
Creates a projection of the input wire unto the input face.
|
|
1672
|
+
|
|
1673
|
+
Parameters
|
|
1674
|
+
----------
|
|
1675
|
+
wire : topologic.Wire
|
|
1676
|
+
The input wire.
|
|
1677
|
+
face : topologic.Face
|
|
1678
|
+
The face unto which to project the input wire.
|
|
1679
|
+
direction : list, optional
|
|
1680
|
+
The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
|
|
1681
|
+
mantissa : int , optional
|
|
1682
|
+
The desired length of the mantissa. The default is 4.
|
|
1683
|
+
tolerance : float , optional
|
|
1684
|
+
The desired tolerance. The default is 0.0001.
|
|
1685
|
+
|
|
1686
|
+
Returns
|
|
1687
|
+
-------
|
|
1688
|
+
topologic.Wire
|
|
1689
|
+
The projected wire.
|
|
1690
|
+
|
|
1691
|
+
"""
|
|
1692
|
+
from topologicpy.Vertex import Vertex
|
|
1693
|
+
from topologicpy.Edge import Edge
|
|
1694
|
+
from topologicpy.Face import Face
|
|
1695
|
+
if not wire:
|
|
1696
|
+
return None
|
|
1697
|
+
if not isinstance(wire, topologic.Wire):
|
|
1698
|
+
return None
|
|
1699
|
+
if not face:
|
|
1700
|
+
return None
|
|
1701
|
+
if not isinstance(face, topologic.Face):
|
|
1702
|
+
return None
|
|
1703
|
+
if not direction:
|
|
1704
|
+
direction = -1*Face.NormalAtParameters(face, 0.5, 0.5, "XYZ", mantissa)
|
|
1705
|
+
large_face = Topology.Scale(face, face.CenterOfMass(), 500, 500, 500)
|
|
1706
|
+
edges = []
|
|
1707
|
+
_ = wire.Edges(None, edges)
|
|
1708
|
+
projected_edges = []
|
|
1709
|
+
|
|
1710
|
+
if large_face:
|
|
1711
|
+
if (large_face.Type() == Face.Type()):
|
|
1712
|
+
for edge in edges:
|
|
1713
|
+
if edge:
|
|
1714
|
+
if (edge.Type() == topologic.Edge.Type()):
|
|
1715
|
+
sv = edge.StartVertex()
|
|
1716
|
+
ev = edge.EndVertex()
|
|
1717
|
+
|
|
1718
|
+
psv = Vertex.Project(vertex=sv, face=large_face, direction=direction)
|
|
1719
|
+
pev = Vertex.Project(vertex=ev, face=large_face, direction=direction)
|
|
1720
|
+
if psv and pev:
|
|
1721
|
+
try:
|
|
1722
|
+
pe = Edge.ByVertices([psv, pev])
|
|
1723
|
+
projected_edges.append(pe)
|
|
1724
|
+
except:
|
|
1725
|
+
continue
|
|
1726
|
+
w = Wire.ByEdges(projected_edges)
|
|
1727
|
+
return w
|
|
1728
|
+
|
|
1729
|
+
@staticmethod
|
|
1730
|
+
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.Wire:
|
|
1731
|
+
"""
|
|
1732
|
+
Creates a rectangle.
|
|
1733
|
+
|
|
1734
|
+
Parameters
|
|
1735
|
+
----------
|
|
1736
|
+
origin : topologic.Vertex , optional
|
|
1737
|
+
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
|
|
1738
|
+
width : float , optional
|
|
1739
|
+
The width of the rectangle. The default is 1.0.
|
|
1740
|
+
length : float , optional
|
|
1741
|
+
The length of the rectangle. The default is 1.0.
|
|
1742
|
+
direction : list , optional
|
|
1743
|
+
The vector representing the up direction of the rectangle. The default is [0,0,1].
|
|
1744
|
+
placement : str , optional
|
|
1745
|
+
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".
|
|
1746
|
+
tolerance : float , optional
|
|
1747
|
+
The desired tolerance. The default is 0.0001.
|
|
1748
|
+
|
|
1749
|
+
Returns
|
|
1750
|
+
-------
|
|
1751
|
+
topologic.Wire
|
|
1752
|
+
The created rectangle.
|
|
1753
|
+
|
|
1754
|
+
"""
|
|
1755
|
+
from topologicpy.Vertex import Vertex
|
|
1756
|
+
from topologicpy.Topology import Topology
|
|
1757
|
+
if not origin:
|
|
1758
|
+
origin = Vertex.ByCoordinates(0,0,0)
|
|
1759
|
+
if not isinstance(origin, topologic.Vertex):
|
|
1760
|
+
print("Wire.Rectangle - Error: specified origin is not a topologic vertex. Retruning None.")
|
|
1761
|
+
return None
|
|
1762
|
+
if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
|
|
1763
|
+
print("Wire.Rectangle - Error: Could not find placement in the list of placements. Retruning None.")
|
|
1764
|
+
return None
|
|
1765
|
+
width = abs(width)
|
|
1766
|
+
length = abs(length)
|
|
1767
|
+
if width < tolerance or length < tolerance:
|
|
1768
|
+
print("Wire.Rectangle - Error: One or more of the specified dimensions is below the tolerance value. Retruning None.")
|
|
1769
|
+
return None
|
|
1770
|
+
if (abs(direction[0]) + abs(direction[1]) + abs(direction[2])) < tolerance:
|
|
1771
|
+
print("Wire.Rectangle - Error: The direction vector magnitude is below the tolerance value. Retruning None.")
|
|
1772
|
+
return None
|
|
1773
|
+
xOffset = 0
|
|
1774
|
+
yOffset = 0
|
|
1775
|
+
if placement.lower() == "lowerleft":
|
|
1776
|
+
xOffset = width*0.5
|
|
1777
|
+
yOffset = length*0.5
|
|
1778
|
+
elif placement.lower() == "upperleft":
|
|
1779
|
+
xOffset = width*0.5
|
|
1780
|
+
yOffset = -length*0.5
|
|
1781
|
+
elif placement.lower() == "lowerright":
|
|
1782
|
+
xOffset = -width*0.5
|
|
1783
|
+
yOffset = length*0.5
|
|
1784
|
+
elif placement.lower() == "upperright":
|
|
1785
|
+
xOffset = -width*0.5
|
|
1786
|
+
yOffset = -length*0.5
|
|
1787
|
+
|
|
1788
|
+
vb1 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
|
|
1789
|
+
vb2 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
|
|
1790
|
+
vb3 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
|
|
1791
|
+
vb4 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
|
|
1792
|
+
|
|
1793
|
+
baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], True)
|
|
1794
|
+
x1 = origin.X()
|
|
1795
|
+
y1 = origin.Y()
|
|
1796
|
+
z1 = origin.Z()
|
|
1797
|
+
x2 = origin.X() + direction[0]
|
|
1798
|
+
y2 = origin.Y() + direction[1]
|
|
1799
|
+
z2 = origin.Z() + direction[2]
|
|
1800
|
+
dx = x2 - x1
|
|
1801
|
+
dy = y2 - y1
|
|
1802
|
+
dz = z2 - z1
|
|
1803
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
1804
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
1805
|
+
if dist < 0.0001:
|
|
1806
|
+
theta = 0
|
|
1807
|
+
else:
|
|
1808
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
1809
|
+
baseWire = Topology.Rotate(baseWire, origin, 0, 1, 0, theta)
|
|
1810
|
+
baseWire = Topology.Rotate(baseWire, origin, 0, 0, 1, phi)
|
|
1811
|
+
return baseWire
|
|
1812
|
+
|
|
1813
|
+
@staticmethod
|
|
1814
|
+
def RemoveCollinearEdges(wire: topologic.Wire, angTolerance: float = 0.1) -> topologic.Wire:
|
|
1815
|
+
"""
|
|
1816
|
+
Removes any collinear edges in the input wire.
|
|
1817
|
+
|
|
1818
|
+
Parameters
|
|
1819
|
+
----------
|
|
1820
|
+
wire : topologic.Wire
|
|
1821
|
+
The input wire.
|
|
1822
|
+
angTolerance : float , optional
|
|
1823
|
+
The desired angular tolerance. The default is 0.1.
|
|
1824
|
+
|
|
1825
|
+
Returns
|
|
1826
|
+
-------
|
|
1827
|
+
topologic.Wire
|
|
1828
|
+
The created wire without any collinear edges.
|
|
1829
|
+
|
|
1830
|
+
"""
|
|
1831
|
+
from topologicpy.Edge import Edge
|
|
1832
|
+
from topologicpy.Wire import Wire
|
|
1833
|
+
from topologicpy.Topology import Topology
|
|
1834
|
+
def rce(wire, angTolerance=0.1):
|
|
1835
|
+
if not isinstance(wire, topologic.Wire):
|
|
1836
|
+
return None
|
|
1837
|
+
final_wire = None
|
|
1838
|
+
vertices = []
|
|
1839
|
+
wire_verts = []
|
|
1840
|
+
try:
|
|
1841
|
+
_ = wire.Vertices(None, vertices)
|
|
1842
|
+
except:
|
|
1843
|
+
return None
|
|
1844
|
+
for aVertex in vertices:
|
|
1845
|
+
edges = []
|
|
1846
|
+
_ = aVertex.Edges(wire, edges)
|
|
1847
|
+
if len(edges) > 1:
|
|
1848
|
+
if not Edge.IsCollinear(edges[0], edges[1], angTolerance=angTolerance):
|
|
1849
|
+
wire_verts.append(aVertex)
|
|
1850
|
+
else:
|
|
1851
|
+
wire_verts.append(aVertex)
|
|
1852
|
+
if len(wire_verts) > 2:
|
|
1853
|
+
if wire.IsClosed():
|
|
1854
|
+
final_wire = Wire.ByVertices(wire_verts, True)
|
|
1855
|
+
else:
|
|
1856
|
+
final_wire = Wire.ByVertices(wire_verts, False)
|
|
1857
|
+
elif len(wire_verts) == 2:
|
|
1858
|
+
final_wire = topologic.Edge.ByStartVertexEndVertex(wire_verts[0], wire_verts[1])
|
|
1859
|
+
return final_wire
|
|
1860
|
+
|
|
1861
|
+
if not topologic.Topology.IsManifold(wire, wire):
|
|
1862
|
+
wires = Wire.Split(wire)
|
|
1863
|
+
else:
|
|
1864
|
+
wires = [wire]
|
|
1865
|
+
returnWires = []
|
|
1866
|
+
for aWire in wires:
|
|
1867
|
+
if not isinstance(aWire, topologic.Wire):
|
|
1868
|
+
returnWires.append(aWire)
|
|
1869
|
+
else:
|
|
1870
|
+
returnWires.append(rce(aWire, angTolerance=angTolerance))
|
|
1871
|
+
if len(returnWires) == 1:
|
|
1872
|
+
returnWire = returnWires[0]
|
|
1873
|
+
if isinstance(returnWire, topologic.Edge):
|
|
1874
|
+
return Wire.ByEdges([returnWire])
|
|
1875
|
+
elif isinstance(returnWire, topologic.Wire):
|
|
1876
|
+
return returnWire
|
|
1877
|
+
else:
|
|
1878
|
+
return None
|
|
1879
|
+
elif len(returnWires) > 1:
|
|
1880
|
+
returnWire = topologic.Cluster.ByTopologies(returnWires).SelfMerge()
|
|
1881
|
+
if isinstance(returnWire, topologic.Edge):
|
|
1882
|
+
return Wire.ByEdges([returnWire])
|
|
1883
|
+
elif isinstance(returnWire, topologic.Wire):
|
|
1884
|
+
return returnWire
|
|
1885
|
+
else:
|
|
1886
|
+
return None
|
|
1887
|
+
else:
|
|
1888
|
+
return None
|
|
1889
|
+
|
|
1890
|
+
def Roof(face, degree=45, tolerance=0.001):
|
|
1891
|
+
"""
|
|
1892
|
+
Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
|
1893
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
|
1894
|
+
|
|
1895
|
+
Parameters
|
|
1896
|
+
----------
|
|
1897
|
+
face : topologic.Face
|
|
1898
|
+
The input face.
|
|
1899
|
+
degree : float , optioal
|
|
1900
|
+
The desired angle in degrees of the roof. The default is 45.
|
|
1901
|
+
tolerance : float , optional
|
|
1902
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
|
1903
|
+
|
|
1904
|
+
Returns
|
|
1905
|
+
-------
|
|
1906
|
+
topologic.Wire
|
|
1907
|
+
The created roof. This method returns the roof as a set of edges. No faces are created.
|
|
1908
|
+
|
|
1909
|
+
"""
|
|
1910
|
+
from topologicpy import Polyskel
|
|
1911
|
+
from topologicpy.Vertex import Vertex
|
|
1912
|
+
from topologicpy.Edge import Edge
|
|
1913
|
+
from topologicpy.Face import Face
|
|
1914
|
+
from topologicpy.Cluster import Cluster
|
|
1915
|
+
from topologicpy.Topology import Topology
|
|
1916
|
+
from topologicpy.Dictionary import Dictionary
|
|
1917
|
+
from topologicpy.Helper import Helper
|
|
1918
|
+
import topologic
|
|
1919
|
+
import math
|
|
1920
|
+
|
|
1921
|
+
def subtrees_to_edges(subtrees, polygon, slope):
|
|
1922
|
+
polygon_z = {}
|
|
1923
|
+
for x, y, z in polygon:
|
|
1924
|
+
polygon_z[(x, y)] = z
|
|
1925
|
+
|
|
1926
|
+
edges = []
|
|
1927
|
+
for subtree in subtrees:
|
|
1928
|
+
source = subtree.source
|
|
1929
|
+
height = subtree.height
|
|
1930
|
+
z = slope * height
|
|
1931
|
+
source_vertex = Vertex.ByCoordinates(source.x, source.y, z)
|
|
1932
|
+
|
|
1933
|
+
for sink in subtree.sinks:
|
|
1934
|
+
if (sink.x, sink.y) in polygon_z:
|
|
1935
|
+
z = 0
|
|
1936
|
+
else:
|
|
1937
|
+
z = None
|
|
1938
|
+
for st in subtrees:
|
|
1939
|
+
if st.source.x == sink.x and st.source.y == sink.y:
|
|
1940
|
+
z = slope * st.height
|
|
1941
|
+
break
|
|
1942
|
+
for sk in st.sinks:
|
|
1943
|
+
if sk.x == sink.x and sk.y == sink.y:
|
|
1944
|
+
z = slope * st.height
|
|
1945
|
+
break
|
|
1946
|
+
if z is None:
|
|
1947
|
+
height = subtree.height
|
|
1948
|
+
z = slope * height
|
|
1949
|
+
sink_vertex = Vertex.ByCoordinates(sink.x, sink.y, z)
|
|
1950
|
+
if (source.x, source.y) == (sink.x, sink.y):
|
|
1951
|
+
continue
|
|
1952
|
+
if Edge.ByStartVertexEndVertex(source_vertex, sink_vertex) not in edges:
|
|
1953
|
+
edges.append(Edge.ByStartVertexEndVertex(source_vertex, sink_vertex))
|
|
1954
|
+
return edges
|
|
1955
|
+
|
|
1956
|
+
def face_to_skeleton(face, degree=0):
|
|
1957
|
+
normal = Face.Normal(face)
|
|
1958
|
+
eb_wire = Face.ExternalBoundary(face)
|
|
1959
|
+
ib_wires = Face.InternalBoundaries(face)
|
|
1960
|
+
eb_vertices = Topology.Vertices(eb_wire)
|
|
1961
|
+
if normal[2] > 0:
|
|
1962
|
+
eb_vertices = list(reversed(eb_vertices))
|
|
1963
|
+
eb_polygon_coordinates = [(v.X(), v.Y(), v.Z()) for v in eb_vertices]
|
|
1964
|
+
eb_polygonxy = [(x[0], x[1]) for x in eb_polygon_coordinates]
|
|
1965
|
+
|
|
1966
|
+
ib_polygonsxy = []
|
|
1967
|
+
zero_coordinates = eb_polygon_coordinates
|
|
1968
|
+
for ib_wire in ib_wires:
|
|
1969
|
+
ib_vertices = Topology.Vertices(ib_wire)
|
|
1970
|
+
if normal[2] > 0:
|
|
1971
|
+
ib_vertices = list(reversed(ib_vertices))
|
|
1972
|
+
ib_polygon_coordinates = [(v.X(), v.Y(), v.Z()) for v in ib_vertices]
|
|
1973
|
+
ib_polygonxy = [(x[0], x[1]) for x in ib_polygon_coordinates]
|
|
1974
|
+
ib_polygonsxy.append(ib_polygonxy)
|
|
1975
|
+
zero_coordinates += ib_polygon_coordinates
|
|
1976
|
+
skeleton = Polyskel.skeletonize(eb_polygonxy, ib_polygonsxy)
|
|
1977
|
+
slope = math.tan(math.radians(degree))
|
|
1978
|
+
roofEdges = subtrees_to_edges(skeleton, zero_coordinates, slope)
|
|
1979
|
+
roofEdges = Helper.Flatten(roofEdges)+Topology.Edges(face)
|
|
1980
|
+
roofTopology = Topology.SelfMerge(Cluster.ByTopologies(roofEdges))
|
|
1981
|
+
return roofTopology
|
|
1982
|
+
|
|
1983
|
+
if not isinstance(face, topologic.Face):
|
|
1984
|
+
return None
|
|
1985
|
+
degree = abs(degree)
|
|
1986
|
+
if degree >= 90-tolerance:
|
|
1987
|
+
return None
|
|
1988
|
+
flat_face = Face.Flatten(face)
|
|
1989
|
+
d = Topology.Dictionary(flat_face)
|
|
1990
|
+
roof = face_to_skeleton(flat_face, degree)
|
|
1991
|
+
if not roof:
|
|
1992
|
+
return None
|
|
1993
|
+
xTran = Dictionary.ValueAtKey(d,"xTran")
|
|
1994
|
+
yTran = Dictionary.ValueAtKey(d,"yTran")
|
|
1995
|
+
zTran = Dictionary.ValueAtKey(d,"zTran")
|
|
1996
|
+
phi = Dictionary.ValueAtKey(d,"phi")
|
|
1997
|
+
theta = Dictionary.ValueAtKey(d,"theta")
|
|
1998
|
+
roof = Topology.Rotate(roof, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
|
|
1999
|
+
roof = Topology.Rotate(roof, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
|
|
2000
|
+
roof = Topology.Translate(roof, xTran, yTran, zTran)
|
|
2001
|
+
return roof
|
|
2002
|
+
|
|
2003
|
+
def Skeleton(face, tolerance=0.001):
|
|
2004
|
+
"""
|
|
2005
|
+
Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
|
2006
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
|
2007
|
+
|
|
2008
|
+
|
|
2009
|
+
Parameters
|
|
2010
|
+
----------
|
|
2011
|
+
face : topologic.Face
|
|
2012
|
+
The input face.
|
|
2013
|
+
|
|
2014
|
+
tolerance : float , optional
|
|
2015
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
|
2016
|
+
|
|
2017
|
+
Returns
|
|
2018
|
+
-------
|
|
2019
|
+
topologic.Wire
|
|
2020
|
+
The created straight skeleton.
|
|
2021
|
+
|
|
2022
|
+
"""
|
|
2023
|
+
if not isinstance(face, topologic.Face):
|
|
2024
|
+
return None
|
|
2025
|
+
return Wire.Roof(face, degree=0, tolerance=tolerance)
|
|
2026
|
+
|
|
2027
|
+
@staticmethod
|
|
2028
|
+
def Split(wire: topologic.Wire) -> list:
|
|
2029
|
+
"""
|
|
2030
|
+
Splits the input wire into segments at its intersections (i.e. at any vertex where more than two edges meet).
|
|
2031
|
+
|
|
2032
|
+
Parameters
|
|
2033
|
+
----------
|
|
2034
|
+
wire : topologic.Wire
|
|
2035
|
+
The input wire.
|
|
2036
|
+
|
|
2037
|
+
Returns
|
|
2038
|
+
-------
|
|
2039
|
+
list
|
|
2040
|
+
The list of split wire segments.
|
|
2041
|
+
|
|
2042
|
+
"""
|
|
2043
|
+
|
|
2044
|
+
def vertexDegree(v, wire):
|
|
2045
|
+
edges = []
|
|
2046
|
+
_ = v.Edges(wire, edges)
|
|
2047
|
+
return len(edges)
|
|
2048
|
+
|
|
2049
|
+
def vertexOtherEdge(vertex, edge, wire):
|
|
2050
|
+
edges = []
|
|
2051
|
+
_ = vertex.Edges(wire, edges)
|
|
2052
|
+
if topologic.Topology.IsSame(edges[0], edge):
|
|
2053
|
+
return edges[-1]
|
|
2054
|
+
else:
|
|
2055
|
+
return edges[0]
|
|
2056
|
+
|
|
2057
|
+
def edgeOtherVertex(edge, vertex):
|
|
2058
|
+
vertices = []
|
|
2059
|
+
_ = edge.Vertices(None, vertices)
|
|
2060
|
+
if topologic.Topology.IsSame(vertex, vertices[0]):
|
|
2061
|
+
return vertices[-1]
|
|
2062
|
+
else:
|
|
2063
|
+
return vertices[0]
|
|
2064
|
+
|
|
2065
|
+
def edgeInList(edge, edgeList):
|
|
2066
|
+
for anEdge in edgeList:
|
|
2067
|
+
if topologic.Topology.IsSame(anEdge, edge):
|
|
2068
|
+
return True
|
|
2069
|
+
return False
|
|
2070
|
+
|
|
2071
|
+
vertices = []
|
|
2072
|
+
_ = wire.Vertices(None, vertices)
|
|
2073
|
+
hubs = []
|
|
2074
|
+
for aVertex in vertices:
|
|
2075
|
+
if vertexDegree(aVertex, wire) > 2:
|
|
2076
|
+
hubs.append(aVertex)
|
|
2077
|
+
wires = []
|
|
2078
|
+
global_edges = []
|
|
2079
|
+
for aVertex in hubs:
|
|
2080
|
+
hub_edges = []
|
|
2081
|
+
_ = aVertex.Edges(wire, hub_edges)
|
|
2082
|
+
wire_edges = []
|
|
2083
|
+
for hub_edge in hub_edges:
|
|
2084
|
+
if not edgeInList(hub_edge, global_edges):
|
|
2085
|
+
current_edge = hub_edge
|
|
2086
|
+
oe = edgeOtherVertex(current_edge, aVertex)
|
|
2087
|
+
while vertexDegree(oe, wire) == 2:
|
|
2088
|
+
if not edgeInList(current_edge, global_edges):
|
|
2089
|
+
global_edges.append(current_edge)
|
|
2090
|
+
wire_edges.append(current_edge)
|
|
2091
|
+
current_edge = vertexOtherEdge(oe, current_edge, wire)
|
|
2092
|
+
oe = edgeOtherVertex(current_edge, oe)
|
|
2093
|
+
if not edgeInList(current_edge, global_edges):
|
|
2094
|
+
global_edges.append(current_edge)
|
|
2095
|
+
wire_edges.append(current_edge)
|
|
2096
|
+
if len(wire_edges) > 1:
|
|
2097
|
+
wires.append(topologic.Cluster.ByTopologies(wire_edges).SelfMerge())
|
|
2098
|
+
else:
|
|
2099
|
+
wires.append(wire_edges[0])
|
|
2100
|
+
wire_edges = []
|
|
2101
|
+
if len(wires) < 1:
|
|
2102
|
+
return [wire]
|
|
2103
|
+
return wires
|
|
2104
|
+
|
|
2105
|
+
@staticmethod
|
|
2106
|
+
def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
|
|
2107
|
+
"""
|
|
2108
|
+
Creates a square.
|
|
2109
|
+
|
|
2110
|
+
Parameters
|
|
2111
|
+
----------
|
|
2112
|
+
origin : topologic.Vertex , optional
|
|
2113
|
+
The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
|
|
2114
|
+
size : float , optional
|
|
2115
|
+
The size of the square. The default is 1.0.
|
|
2116
|
+
direction : list , optional
|
|
2117
|
+
The vector representing the up direction of the square. The default is [0,0,1].
|
|
2118
|
+
placement : str , optional
|
|
2119
|
+
The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
|
|
2120
|
+
tolerance : float , optional
|
|
2121
|
+
The desired tolerance. The default is 0.0001.
|
|
2122
|
+
|
|
2123
|
+
Returns
|
|
2124
|
+
-------
|
|
2125
|
+
topologic.Wire
|
|
2126
|
+
The created square.
|
|
2127
|
+
|
|
2128
|
+
"""
|
|
2129
|
+
return Wire.Rectangle(origin = origin, width = size, length = size, direction = direction, placement = placement, tolerance = tolerance)
|
|
2130
|
+
|
|
2131
|
+
@staticmethod
|
|
2132
|
+
def Star(origin: topologic.Wire = None, radiusA: float = 0.5, radiusB: float = 0.2, rays: int = 8, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
|
|
2133
|
+
"""
|
|
2134
|
+
Creates a star.
|
|
2135
|
+
|
|
2136
|
+
Parameters
|
|
2137
|
+
----------
|
|
2138
|
+
origin : topologic.Vertex , optional
|
|
2139
|
+
The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
|
|
2140
|
+
radiusA : float , optional
|
|
2141
|
+
The outer radius of the star. The default is 1.0.
|
|
2142
|
+
radiusB : float , optional
|
|
2143
|
+
The outer radius of the star. The default is 0.4.
|
|
2144
|
+
rays : int , optional
|
|
2145
|
+
The number of star rays. The default is 8.
|
|
2146
|
+
direction : list , optional
|
|
2147
|
+
The vector representing the up direction of the star. The default is [0,0,1].
|
|
2148
|
+
placement : str , optional
|
|
2149
|
+
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".
|
|
2150
|
+
tolerance : float , optional
|
|
2151
|
+
The desired tolerance. The default is 0.0001.
|
|
2152
|
+
|
|
2153
|
+
Returns
|
|
2154
|
+
-------
|
|
2155
|
+
topologic.Wire
|
|
2156
|
+
The created star.
|
|
2157
|
+
|
|
2158
|
+
"""
|
|
2159
|
+
|
|
2160
|
+
if not origin:
|
|
2161
|
+
origin = topologic.Vertex.ByCoordinates(0,0,0)
|
|
2162
|
+
if not isinstance(origin, topologic.Vertex):
|
|
2163
|
+
return None
|
|
2164
|
+
radiusA = abs(radiusA)
|
|
2165
|
+
radiusB = abs(radiusB)
|
|
2166
|
+
if radiusA < tolerance or radiusB < tolerance:
|
|
2167
|
+
return None
|
|
2168
|
+
rays = abs(rays)
|
|
2169
|
+
if rays < 3:
|
|
2170
|
+
return None
|
|
2171
|
+
if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
|
|
2172
|
+
return None
|
|
2173
|
+
sides = rays*2 # Sides is double the number of rays
|
|
2174
|
+
baseV = []
|
|
2175
|
+
|
|
2176
|
+
xList = []
|
|
2177
|
+
yList = []
|
|
2178
|
+
for i in range(sides):
|
|
2179
|
+
if i%2 == 0:
|
|
2180
|
+
radius = radiusA
|
|
2181
|
+
else:
|
|
2182
|
+
radius = radiusB
|
|
2183
|
+
angle = math.radians(360/sides)*i
|
|
2184
|
+
x = math.sin(angle)*radius + origin.X()
|
|
2185
|
+
y = math.cos(angle)*radius + origin.Y()
|
|
2186
|
+
z = origin.Z()
|
|
2187
|
+
xList.append(x)
|
|
2188
|
+
yList.append(y)
|
|
2189
|
+
baseV.append([x,y])
|
|
2190
|
+
|
|
2191
|
+
if placement.lower() == "lowerleft":
|
|
2192
|
+
xmin = min(xList)
|
|
2193
|
+
ymin = min(yList)
|
|
2194
|
+
xOffset = origin.X() - xmin
|
|
2195
|
+
yOffset = origin.Y() - ymin
|
|
2196
|
+
elif placement.lower() == "upperleft":
|
|
2197
|
+
xmin = min(xList)
|
|
2198
|
+
ymax = max(yList)
|
|
2199
|
+
xOffset = origin.X() - xmin
|
|
2200
|
+
yOffset = origin.Y() - ymax
|
|
2201
|
+
elif placement.lower() == "lowerright":
|
|
2202
|
+
xmax = max(xList)
|
|
2203
|
+
ymin = min(yList)
|
|
2204
|
+
xOffset = origin.X() - xmax
|
|
2205
|
+
yOffset = origin.Y() - ymin
|
|
2206
|
+
elif placement.lower() == "upperright":
|
|
2207
|
+
xmax = max(xList)
|
|
2208
|
+
ymax = max(yList)
|
|
2209
|
+
xOffset = origin.X() - xmax
|
|
2210
|
+
yOffset = origin.Y() - ymax
|
|
2211
|
+
else:
|
|
2212
|
+
xOffset = 0
|
|
2213
|
+
yOffset = 0
|
|
2214
|
+
tranBase = []
|
|
2215
|
+
for coord in baseV:
|
|
2216
|
+
tranBase.append(topologic.Vertex.ByCoordinates(coord[0]+xOffset, coord[1]+yOffset, origin.Z()))
|
|
2217
|
+
|
|
2218
|
+
baseWire = Wire.ByVertices(tranBase[::-1], True) #reversing the list so that the normal points up in Blender
|
|
2219
|
+
|
|
2220
|
+
x1 = origin.X()
|
|
2221
|
+
y1 = origin.Y()
|
|
2222
|
+
z1 = origin.Z()
|
|
2223
|
+
x2 = origin.X() + direction[0]
|
|
2224
|
+
y2 = origin.Y() + direction[1]
|
|
2225
|
+
z2 = origin.Z() + direction[2]
|
|
2226
|
+
dx = x2 - x1
|
|
2227
|
+
dy = y2 - y1
|
|
2228
|
+
dz = z2 - z1
|
|
2229
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
2230
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Z-Axis
|
|
2231
|
+
if dist < 0.0001:
|
|
2232
|
+
theta = 0
|
|
2233
|
+
else:
|
|
2234
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Y-Axis
|
|
2235
|
+
baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 1, 0, theta)
|
|
2236
|
+
baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 0, 1, phi)
|
|
2237
|
+
return baseWire
|
|
2238
|
+
|
|
2239
|
+
@staticmethod
|
|
2240
|
+
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.Wire:
|
|
2241
|
+
"""
|
|
2242
|
+
Creates a trapezoid.
|
|
2243
|
+
|
|
2244
|
+
Parameters
|
|
2245
|
+
----------
|
|
2246
|
+
origin : topologic.Vertex , optional
|
|
2247
|
+
The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
|
|
2248
|
+
widthA : float , optional
|
|
2249
|
+
The width of the bottom edge of the trapezoid. The default is 1.0.
|
|
2250
|
+
widthB : float , optional
|
|
2251
|
+
The width of the top edge of the trapezoid. The default is 0.75.
|
|
2252
|
+
offsetA : float , optional
|
|
2253
|
+
The offset of the bottom edge of the trapezoid. The default is 0.0.
|
|
2254
|
+
offsetB : float , optional
|
|
2255
|
+
The offset of the top edge of the trapezoid. The default is 0.0.
|
|
2256
|
+
length : float , optional
|
|
2257
|
+
The length of the trapezoid. The default is 1.0.
|
|
2258
|
+
direction : list , optional
|
|
2259
|
+
The vector representing the up direction of the trapezoid. The default is [0,0,1].
|
|
2260
|
+
placement : str , optional
|
|
2261
|
+
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".
|
|
2262
|
+
tolerance : float , optional
|
|
2263
|
+
The desired tolerance. The default is 0.0001.
|
|
2264
|
+
|
|
2265
|
+
Returns
|
|
2266
|
+
-------
|
|
2267
|
+
topologic.Wire
|
|
2268
|
+
The created trapezoid.
|
|
2269
|
+
|
|
2270
|
+
"""
|
|
2271
|
+
if not origin:
|
|
2272
|
+
origin = topologic.Vertex.ByCoordinates(0,0,0)
|
|
2273
|
+
if not isinstance(origin, topologic.Vertex):
|
|
2274
|
+
return None
|
|
2275
|
+
widthA = abs(widthA)
|
|
2276
|
+
widthB = abs(widthB)
|
|
2277
|
+
length = abs(length)
|
|
2278
|
+
if widthA < tolerance or widthB < tolerance or length < tolerance:
|
|
2279
|
+
return None
|
|
2280
|
+
if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
|
|
2281
|
+
return None
|
|
2282
|
+
xOffset = 0
|
|
2283
|
+
yOffset = 0
|
|
2284
|
+
if placement.lower() == "center":
|
|
2285
|
+
xOffset = -((-widthA*0.5 + offsetA) + (-widthB*0.5 + offsetB) + (widthA*0.5 + offsetA) + (widthB*0.5 + offsetB))/4.0
|
|
2286
|
+
yOffset = 0
|
|
2287
|
+
elif placement.lower() == "lowerleft":
|
|
2288
|
+
xOffset = -(min((-widthA*0.5 + offsetA), (-widthB*0.5 + offsetB)))
|
|
2289
|
+
yOffset = length*0.5
|
|
2290
|
+
elif placement.lower() == "upperleft":
|
|
2291
|
+
xOffset = -(min((-widthA*0.5 + offsetA), (-widthB*0.5 + offsetB)))
|
|
2292
|
+
yOffset = -length*0.5
|
|
2293
|
+
elif placement.lower() == "lowerright":
|
|
2294
|
+
xOffset = -(max((widthA*0.5 + offsetA), (widthB*0.5 + offsetB)))
|
|
2295
|
+
yOffset = length*0.5
|
|
2296
|
+
elif placement.lower() == "upperright":
|
|
2297
|
+
xOffset = -(max((widthA*0.5 + offsetA), (widthB*0.5 + offsetB)))
|
|
2298
|
+
yOffset = -length*0.5
|
|
2299
|
+
|
|
2300
|
+
vb1 = topologic.Vertex.ByCoordinates(origin.X()-widthA*0.5+offsetA+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
|
|
2301
|
+
vb2 = topologic.Vertex.ByCoordinates(origin.X()+widthA*0.5+offsetA+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
|
|
2302
|
+
vb3 = topologic.Vertex.ByCoordinates(origin.X()+widthB*0.5+offsetB+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
|
|
2303
|
+
vb4 = topologic.Vertex.ByCoordinates(origin.X()-widthB*0.5++offsetB+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
|
|
2304
|
+
|
|
2305
|
+
baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], True)
|
|
2306
|
+
x1 = origin.X()
|
|
2307
|
+
y1 = origin.Y()
|
|
2308
|
+
z1 = origin.Z()
|
|
2309
|
+
x2 = origin.X() + direction[0]
|
|
2310
|
+
y2 = origin.Y() + direction[1]
|
|
2311
|
+
z2 = origin.Z() + direction[2]
|
|
2312
|
+
dx = x2 - x1
|
|
2313
|
+
dy = y2 - y1
|
|
2314
|
+
dz = z2 - z1
|
|
2315
|
+
dist = math.sqrt(dx**2 + dy**2 + dz**2)
|
|
2316
|
+
phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
|
|
2317
|
+
if dist < 0.0001:
|
|
2318
|
+
theta = 0
|
|
2319
|
+
else:
|
|
2320
|
+
theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
|
|
2321
|
+
baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 1, 0, theta)
|
|
2322
|
+
baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 0, 1, phi)
|
|
2323
|
+
return baseWire
|
|
2324
|
+
|
|
2325
|
+
@staticmethod
|
|
2326
|
+
def Vertices(wire: topologic.Wire) -> list:
|
|
2327
|
+
"""
|
|
2328
|
+
Returns the list of vertices of the input wire.
|
|
2329
|
+
|
|
2330
|
+
Parameters
|
|
2331
|
+
----------
|
|
2332
|
+
wire : topologic.Wire
|
|
2333
|
+
The input wire.
|
|
2334
|
+
|
|
2335
|
+
Returns
|
|
2336
|
+
-------
|
|
2337
|
+
list
|
|
2338
|
+
The list of vertices.
|
|
2339
|
+
|
|
2340
|
+
"""
|
|
2341
|
+
if not isinstance(wire, topologic.Wire):
|
|
2342
|
+
return None
|
|
2343
|
+
vertices = []
|
|
2344
|
+
_ = wire.Vertices(None, vertices)
|
|
2345
|
+
return vertices
|
|
2346
|
+
|