topologicpy 0.8.28__py3-none-any.whl → 0.8.30__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/Plotly.py +9 -5
- topologicpy/ShapeGrammar.py +492 -0
- topologicpy/Topology.py +83 -125
- topologicpy/version.py +1 -1
- {topologicpy-0.8.28.dist-info → topologicpy-0.8.30.dist-info}/METADATA +1 -1
- {topologicpy-0.8.28.dist-info → topologicpy-0.8.30.dist-info}/RECORD +9 -8
- {topologicpy-0.8.28.dist-info → topologicpy-0.8.30.dist-info}/WHEEL +1 -1
- {topologicpy-0.8.28.dist-info → topologicpy-0.8.30.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.28.dist-info → topologicpy-0.8.30.dist-info}/top_level.txt +0 -0
topologicpy/Plotly.py
CHANGED
@@ -592,6 +592,7 @@ class Plotly:
|
|
592
592
|
mode = "markers+text"
|
593
593
|
else:
|
594
594
|
mode = "markers"
|
595
|
+
|
595
596
|
vData2 = go.Scatter3d(x=x,
|
596
597
|
y=y,
|
597
598
|
z=z,
|
@@ -603,11 +604,13 @@ class Plotly:
|
|
603
604
|
opacity=1,
|
604
605
|
sizemode="diameter"),
|
605
606
|
mode=mode,
|
607
|
+
customdata = labels,
|
606
608
|
legendgroup=legendGroup,
|
607
609
|
legendrank=legendRank,
|
608
610
|
text=labels,
|
609
611
|
hoverinfo='text',
|
610
|
-
hovertext=labels
|
612
|
+
hovertext=labels,
|
613
|
+
hovertemplate=["Click "+label for label in labels]
|
611
614
|
)
|
612
615
|
if borderWidth > 0 or borderWidthKey:
|
613
616
|
vData1 = go.Scatter3d(x=x,
|
@@ -676,14 +679,13 @@ class Plotly:
|
|
676
679
|
labels = []
|
677
680
|
for j, elements_cluster in enumerate(elements_clusters):
|
678
681
|
d_color = color
|
679
|
-
labels.append("Edge_"+str(j+1).zfill(n))
|
680
682
|
d = dict_clusters[j][0] # All dicitonaries have same values in dictionaries, so take first one.
|
681
683
|
if d:
|
682
684
|
if not colorKey == None:
|
683
685
|
d_color = Dictionary.ValueAtKey(d, key=colorKey) or color
|
684
686
|
d_color = Color.AnyToHex(d_color)
|
685
687
|
if not labelKey == None:
|
686
|
-
|
688
|
+
labels.append(str(Dictionary.ValueAtKey(d, labelKey, "")))
|
687
689
|
if not widthKey == None:
|
688
690
|
e_width = Dictionary.ValueAtKey(d, key=widthKey)
|
689
691
|
if not e_width == None:
|
@@ -727,8 +729,10 @@ class Plotly:
|
|
727
729
|
line=dict(color=d_color, width=width),
|
728
730
|
legendgroup=legendGroup,
|
729
731
|
legendrank=legendRank,
|
730
|
-
text=
|
731
|
-
|
732
|
+
text=labels,
|
733
|
+
customdata=labels,
|
734
|
+
hoverinfo='text',
|
735
|
+
hovertext=labels)
|
732
736
|
traces.append(trace)
|
733
737
|
else:
|
734
738
|
x = []
|
@@ -0,0 +1,492 @@
|
|
1
|
+
# Copyright (C) 2025
|
2
|
+
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
import topologic_core as topologic
|
18
|
+
|
19
|
+
class ShapeGrammar:
|
20
|
+
def __init__(self):
|
21
|
+
self.title = "Untitled" # Stores the title of the topology grammar.
|
22
|
+
self.description = "" # Stores the description of the grammar.
|
23
|
+
self.rules = [] # Stores transformation rules of the topology grammar.
|
24
|
+
# Operations
|
25
|
+
# Replace
|
26
|
+
replace = {"title": "Replace",
|
27
|
+
"description": "Replaces the input topology with the output topology.",
|
28
|
+
"uSides": None,
|
29
|
+
"vSides": None,
|
30
|
+
"wSides": None}
|
31
|
+
# Transform
|
32
|
+
transform = {"title": "Transform",
|
33
|
+
"description": "Transforms the input topology using the specified matrix.",
|
34
|
+
"uSides": None,
|
35
|
+
"vSides": None,
|
36
|
+
"wSides": None}
|
37
|
+
# Union
|
38
|
+
union = {"title": "Union",
|
39
|
+
"description": "Unions the input topology and the output topology.",
|
40
|
+
"uSides": None,
|
41
|
+
"vSides": None,
|
42
|
+
"wSides": None}
|
43
|
+
# Difference
|
44
|
+
difference = {"title": "Difference",
|
45
|
+
"description": "Subtracts the output topology from the input topology.",
|
46
|
+
"uSides": None,
|
47
|
+
"vSides": None,
|
48
|
+
"wSides": None}
|
49
|
+
# Difference
|
50
|
+
symdif = {"title": "Symmetric Difference",
|
51
|
+
"description": "Calculates the symmetrical difference of the input topology and the output topology.",
|
52
|
+
"uSides": None,
|
53
|
+
"vSides": None,
|
54
|
+
"wSides": None}
|
55
|
+
# Intersect
|
56
|
+
intersect = {"title": "Intersect",
|
57
|
+
"description": "Intersects the input topology and the output topology.",
|
58
|
+
"uSides": None,
|
59
|
+
"vSides": None,
|
60
|
+
"wSides": None}
|
61
|
+
# Merge
|
62
|
+
merge = {"title": "Merge",
|
63
|
+
"description": "Merges the input topology and the output topology.",
|
64
|
+
"uSides": None,
|
65
|
+
"vSides": None,
|
66
|
+
"wSides": None}
|
67
|
+
# Slice
|
68
|
+
slice = {"title": "Slice",
|
69
|
+
"description": "Slices the input topology using the output topology.",
|
70
|
+
"uSides": None,
|
71
|
+
"vSides": None,
|
72
|
+
"wSides": None}
|
73
|
+
# Impose
|
74
|
+
impose = {"title": "Impose",
|
75
|
+
"description": "Imposes the output topology on the input topology.",
|
76
|
+
"uSides": None,
|
77
|
+
"vSides": None,
|
78
|
+
"wSides": None}
|
79
|
+
# Imprint
|
80
|
+
imprint = {"title": "Imprint",
|
81
|
+
"description": "Imposes the output topology on the input topology.",
|
82
|
+
"uSides": None,
|
83
|
+
"vSides": None,
|
84
|
+
"wSides": None}
|
85
|
+
# Divide
|
86
|
+
divide = {"title": "Divide",
|
87
|
+
"description": "Divides the input topology along the x, y, and z axes using the specified number of sides (uSides, vSides, wSides)",
|
88
|
+
"uSides": 2,
|
89
|
+
"vSides": 2,
|
90
|
+
"wSides": 2}
|
91
|
+
self.operations = [replace, transform, union, difference, symdif, intersect, merge, slice, impose, imprint, divide]
|
92
|
+
|
93
|
+
def OperationTitles(self):
|
94
|
+
"""
|
95
|
+
Returns the list of available operation titles.
|
96
|
+
|
97
|
+
Parameters
|
98
|
+
----------
|
99
|
+
|
100
|
+
Returns
|
101
|
+
-------
|
102
|
+
list
|
103
|
+
The requested list of operation titles
|
104
|
+
"""
|
105
|
+
return [op["title"] for op in self.operations]
|
106
|
+
|
107
|
+
def OperationByTitle(self, title):
|
108
|
+
"""
|
109
|
+
Returns the operation given the input title string
|
110
|
+
|
111
|
+
Parameters
|
112
|
+
----------
|
113
|
+
title : str
|
114
|
+
The input operation str. See OperationTitles for list of operations.
|
115
|
+
|
116
|
+
Returns
|
117
|
+
-------
|
118
|
+
ShapeGrammar.Operation
|
119
|
+
The requested operation
|
120
|
+
"""
|
121
|
+
for op in self.operations:
|
122
|
+
op_title = op["title"]
|
123
|
+
if title.lower() in op_title.lower():
|
124
|
+
return op
|
125
|
+
return None
|
126
|
+
|
127
|
+
def AddRule(self,
|
128
|
+
input,
|
129
|
+
output,
|
130
|
+
title : str = "Untitled Rule",
|
131
|
+
description: str = "",
|
132
|
+
operation : dict = None,
|
133
|
+
matrix: list = None,
|
134
|
+
silent: bool = False):
|
135
|
+
"""
|
136
|
+
Adds a rule to the topology grammar.
|
137
|
+
|
138
|
+
Parameters
|
139
|
+
----------
|
140
|
+
input : topologic_core.Topology
|
141
|
+
The linput topology of the rule.
|
142
|
+
output : topologic_core.Topology
|
143
|
+
The output topology of the rule.
|
144
|
+
title : str , optional
|
145
|
+
The title of the rule. The default is "Untitled Rule"
|
146
|
+
description : str, optional
|
147
|
+
The description of the rule. The default is "".
|
148
|
+
operation : dict , optional
|
149
|
+
The desired rule operation. See Rule Operations. If set to None, the replacement rule is applied. The default is None.
|
150
|
+
matrix : list
|
151
|
+
The 4x4 transformation matrix that tranforms the output topology to the input topology. If set to None, no transformation is applied. The default is None.
|
152
|
+
silent : bool, optional
|
153
|
+
If True, suppresses error/warning messages. Default is False.
|
154
|
+
|
155
|
+
Returns
|
156
|
+
-------
|
157
|
+
None
|
158
|
+
This method does not return a value.
|
159
|
+
"""
|
160
|
+
from topologicpy.Topology import Topology
|
161
|
+
|
162
|
+
def is_4x4_matrix(matrix):
|
163
|
+
return (
|
164
|
+
isinstance(matrix, list) and
|
165
|
+
len(matrix) == 4 and
|
166
|
+
all(isinstance(row, list) and len(row) == 4 for row in matrix)
|
167
|
+
)
|
168
|
+
|
169
|
+
if not Topology.IsInstance(input, "Topology"):
|
170
|
+
if not silent:
|
171
|
+
print("ShapeGrammar.AddRule - Error: The input input parameter is not a valid topology. Returning None.")
|
172
|
+
return None
|
173
|
+
if not output == None:
|
174
|
+
if not Topology.IsInstance(output, "Topology"):
|
175
|
+
if not silent:
|
176
|
+
print("ShapeGrammar.AddRule - Error: The input output parameter is not a valid topology. Returning None.")
|
177
|
+
return None
|
178
|
+
if not operation == None:
|
179
|
+
if not operation in self.operations:
|
180
|
+
if not silent:
|
181
|
+
print("ShapeGrammar.AddRule - Error: The input operation parameter is not a valid operation. Returning None.")
|
182
|
+
return None
|
183
|
+
if not matrix == None:
|
184
|
+
if not is_4x4_matrix(matrix):
|
185
|
+
if not silent:
|
186
|
+
print("ShapeGrammar.AddRule - Error: The input matrix parameter is not a valid matrix. Returning None.")
|
187
|
+
return None
|
188
|
+
|
189
|
+
self.rules.append({"input":input,
|
190
|
+
"output": output,
|
191
|
+
"title": title,
|
192
|
+
"description": description,
|
193
|
+
"operation": operation,
|
194
|
+
"matrix": matrix
|
195
|
+
})
|
196
|
+
|
197
|
+
def ApplicableRules(self, topology, keys: list = None, silent: bool = False):
|
198
|
+
"""
|
199
|
+
Returns rules applicable to the input topology.
|
200
|
+
|
201
|
+
Parameters
|
202
|
+
----------
|
203
|
+
topology : topologic_core.Topology
|
204
|
+
The input topology
|
205
|
+
keys : list , optional
|
206
|
+
The list of dictionary keys to semantically match the rules. The default is None which means dictionaries are not considered.
|
207
|
+
silent : bool, optional
|
208
|
+
If True, suppresses error/warning messages. Default is False.
|
209
|
+
|
210
|
+
Returns
|
211
|
+
-------
|
212
|
+
list
|
213
|
+
The list of applicable rules.
|
214
|
+
"""
|
215
|
+
from topologicpy.Topology import Topology
|
216
|
+
from topologicpy.Dictionary import Dictionary
|
217
|
+
|
218
|
+
if not Topology.IsInstance(topology, "Topology"):
|
219
|
+
if not silent:
|
220
|
+
print("ShapeGrammar.ApplicableRules - Error: The input topology parameter is not a valid topology. Returning None.")
|
221
|
+
return None
|
222
|
+
|
223
|
+
ap_rules = []
|
224
|
+
ap_trans = []
|
225
|
+
d = Topology.Dictionary(topology)
|
226
|
+
for i, rule in enumerate(self.rules):
|
227
|
+
dict_status = True
|
228
|
+
input = rule["input"]
|
229
|
+
# If there is a list of keys specified, check that the values match
|
230
|
+
if isinstance(keys, list):
|
231
|
+
d_input = Topology.Dictionary(input)
|
232
|
+
for j, key in enumerate(keys):
|
233
|
+
if not Dictionary.ValueAtKey(d, key, None) == Dictionary.ValueAtKey(d_input, key, None):
|
234
|
+
dict_status = False
|
235
|
+
break
|
236
|
+
#If it passed the dictionary key test, then check topology similarity
|
237
|
+
if dict_status:
|
238
|
+
topology_status, mat = Topology.IsSimilar(rule["input"], topology)
|
239
|
+
if topology_status:
|
240
|
+
ap_rules.append(rule)
|
241
|
+
ap_trans.append(mat)
|
242
|
+
return ap_rules, ap_trans
|
243
|
+
|
244
|
+
def ApplyRule(self, topology, rule: dict = None, matrix: list = None, mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
245
|
+
"""
|
246
|
+
Returns rules applicable to the input topology.
|
247
|
+
|
248
|
+
Parameters
|
249
|
+
----------
|
250
|
+
topology : topologic_core.Topology
|
251
|
+
The input topology
|
252
|
+
rule : dict , optional
|
253
|
+
The desired rule to apply. The default is None.
|
254
|
+
matrix : list
|
255
|
+
The 4x4 transformation matrix that tranforms the output topology to the input topology. If set to None, no transformation is applied. The default is None.
|
256
|
+
mantissa : int, optional
|
257
|
+
Decimal precision. Default is 6.
|
258
|
+
tolerance : float, optional
|
259
|
+
The desired Tolerance. Not used here but included for API compatibility. Default is 0.0001.
|
260
|
+
silent : bool, optional
|
261
|
+
If True, suppresses error/warning messages. Default is False.
|
262
|
+
|
263
|
+
Returns
|
264
|
+
-------
|
265
|
+
topologic_core.Topology
|
266
|
+
The transformed topology
|
267
|
+
"""
|
268
|
+
|
269
|
+
from topologicpy.Topology import Topology
|
270
|
+
from topologicpy.Cluster import Cluster
|
271
|
+
from topologicpy.Face import Face
|
272
|
+
from topologicpy.Vertex import Vertex
|
273
|
+
|
274
|
+
def is_4x4_matrix(matrix):
|
275
|
+
return (
|
276
|
+
isinstance(matrix, list) and
|
277
|
+
len(matrix) == 4 and
|
278
|
+
all(isinstance(row, list) and len(row) == 4 for row in matrix)
|
279
|
+
)
|
280
|
+
|
281
|
+
def bb(topology):
|
282
|
+
vertices = Topology.Vertices(topology)
|
283
|
+
x = []
|
284
|
+
y = []
|
285
|
+
z = []
|
286
|
+
for aVertex in vertices:
|
287
|
+
x.append(Vertex.X(aVertex, mantissa=mantissa))
|
288
|
+
y.append(Vertex.Y(aVertex, mantissa=mantissa))
|
289
|
+
z.append(Vertex.Z(aVertex, mantissa=mantissa))
|
290
|
+
x_min = min(x)
|
291
|
+
y_min = min(y)
|
292
|
+
z_min = min(z)
|
293
|
+
maxX = max(x)
|
294
|
+
maxY = max(y)
|
295
|
+
maxZ = max(z)
|
296
|
+
return [x_min, y_min, z_min, maxX, maxY, maxZ]
|
297
|
+
|
298
|
+
def slice(topology, uSides, vSides, wSides):
|
299
|
+
x_min, y_min, z_min, maxX, maxY, maxZ = bb(topology)
|
300
|
+
centroid = Vertex.ByCoordinates(x_min+(maxX-x_min)*0.5, y_min+(maxY-y_min)*0.5, z_min+(maxZ-z_min)*0.5)
|
301
|
+
wOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), Vertex.Y(centroid, mantissa=mantissa), z_min)
|
302
|
+
wFace = Face.Rectangle(origin=wOrigin, width=(maxX-x_min)*1.1, length=(maxY-y_min)*1.1)
|
303
|
+
wFaces = []
|
304
|
+
wOffset = (maxZ-z_min)/wSides
|
305
|
+
for i in range(wSides-1):
|
306
|
+
wFaces.append(Topology.Translate(wFace, 0,0,wOffset*(i+1)))
|
307
|
+
uOrigin = Vertex.ByCoordinates(x_min, Vertex.Y(centroid, mantissa=mantissa), Vertex.Z(centroid, mantissa=mantissa))
|
308
|
+
uFace = Face.Rectangle(origin=uOrigin, width=(maxZ-z_min)*1.1, length=(maxY-y_min)*1.1, direction=[1,0,0])
|
309
|
+
uFaces = []
|
310
|
+
uOffset = (maxX-x_min)/uSides
|
311
|
+
for i in range(uSides-1):
|
312
|
+
uFaces.append(Topology.Translate(uFace, uOffset*(i+1),0,0))
|
313
|
+
vOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), y_min, Vertex.Z(centroid, mantissa=mantissa))
|
314
|
+
vFace = Face.Rectangle(origin=vOrigin, width=(maxX-x_min)*1.1, length=(maxZ-z_min)*1.1, direction=[0,1,0])
|
315
|
+
vFaces = []
|
316
|
+
vOffset = (maxY-y_min)/vSides
|
317
|
+
for i in range(vSides-1):
|
318
|
+
vFaces.append(Topology.Translate(vFace, 0,vOffset*(i+1),0))
|
319
|
+
all_faces = uFaces+vFaces+wFaces
|
320
|
+
if len(all_faces) > 0:
|
321
|
+
f_clus = Cluster.ByTopologies(uFaces+vFaces+wFaces)
|
322
|
+
return Topology.Slice(topology, f_clus, tolerance=tolerance)
|
323
|
+
else:
|
324
|
+
return topology
|
325
|
+
|
326
|
+
if not Topology.IsInstance(topology, "Topology"):
|
327
|
+
if not silent:
|
328
|
+
print("ShapeGrammar.ApplyRule - Error: The input topology parameter is not a valid topology. Returning None.")
|
329
|
+
return None
|
330
|
+
if not matrix == None:
|
331
|
+
if not is_4x4_matrix(matrix):
|
332
|
+
if not silent:
|
333
|
+
print("ShapeGrammar.ApplyRule - Error: The input matrix parameter is not a valid matrix. Returning None.")
|
334
|
+
return None
|
335
|
+
|
336
|
+
if not rule == None:
|
337
|
+
input = rule["input"]
|
338
|
+
output = rule["output"]
|
339
|
+
r_matrix = rule["matrix"]
|
340
|
+
operation = rule["operation"]
|
341
|
+
if not operation == None:
|
342
|
+
op_title = operation["title"]
|
343
|
+
else:
|
344
|
+
op_title = "None"
|
345
|
+
|
346
|
+
result_output = topology
|
347
|
+
temp_output = None
|
348
|
+
if not output == None:
|
349
|
+
temp_output = output
|
350
|
+
# Transform the output topology to the input topology to prepare it for final transformation
|
351
|
+
if not r_matrix == None and not output == None:
|
352
|
+
temp_output = Topology.Transform(output, r_matrix)
|
353
|
+
|
354
|
+
if "replace" in op_title.lower():
|
355
|
+
result_output = temp_output
|
356
|
+
elif "transform" in op_title.lower():
|
357
|
+
result_output = Topology.Transform(topology, r_matrix)
|
358
|
+
elif "union" in op_title.lower():
|
359
|
+
result_output = Topology.Union(input, temp_output)
|
360
|
+
elif "difference" in op_title.lower():
|
361
|
+
result_output = Topology.Difference(input, temp_output)
|
362
|
+
elif "symmetric difference" in op_title.lower():
|
363
|
+
result_output = Topology.SymmetricDifference(input, temp_output)
|
364
|
+
elif "intersect" in op_title.lower():
|
365
|
+
result_output = Topology.Intersect(input, temp_output)
|
366
|
+
elif "merge" in op_title.lower():
|
367
|
+
result_output = Topology.Merge(input, temp_output)
|
368
|
+
elif "slice" in op_title.lower():
|
369
|
+
result_output = Topology.Slice(input, temp_output)
|
370
|
+
elif "impose" in op_title.lower():
|
371
|
+
result_output = Topology.Impose(input, temp_output)
|
372
|
+
elif "imprint" in op_title.lower():
|
373
|
+
result_output = Topology.Imprint(input, temp_output)
|
374
|
+
elif "divide" in op_title.lower():
|
375
|
+
uSides = operation["uSides"]
|
376
|
+
vSides = operation["vSides"]
|
377
|
+
wSides = operation["wSides"]
|
378
|
+
if not uSides == None and not vSides == None and not wSides == None:
|
379
|
+
result_output = slice(input, uSides, vSides, wSides)
|
380
|
+
|
381
|
+
# Finally, transform the result to the input topology
|
382
|
+
if not matrix == None:
|
383
|
+
result_output = Topology.Transform(result_output, matrix)
|
384
|
+
|
385
|
+
return result_output
|
386
|
+
|
387
|
+
def FigureByInputOutput(self, input, output, silent: bool = False):
|
388
|
+
"""
|
389
|
+
Returns the Plotly figure of the input and output topologies as a rule.
|
390
|
+
|
391
|
+
Parameters
|
392
|
+
----------
|
393
|
+
input : topologic_core.Topology
|
394
|
+
The input topology
|
395
|
+
output : topologic_core.Topology
|
396
|
+
The output topology
|
397
|
+
silent : bool, optional
|
398
|
+
If True, suppresses error/warning messages. Default is False.
|
399
|
+
|
400
|
+
Returns
|
401
|
+
-------
|
402
|
+
This function does not return a value
|
403
|
+
"""
|
404
|
+
|
405
|
+
from topologicpy.Vertex import Vertex
|
406
|
+
from topologicpy.Cell import Cell
|
407
|
+
from topologicpy.Topology import Topology
|
408
|
+
from topologicpy.Dictionary import Dictionary
|
409
|
+
from topologicpy.Cluster import Cluster
|
410
|
+
from topologicpy.Plotly import Plotly
|
411
|
+
|
412
|
+
if not Topology.IsInstance(input, "Topology"):
|
413
|
+
if not silent:
|
414
|
+
print("ShapeGrammar.DrawInputOutput - Error: The input topology parameter is not a valid topology. Returning None.")
|
415
|
+
return None
|
416
|
+
if not Topology.IsInstance(output, "Topology"):
|
417
|
+
if not silent:
|
418
|
+
print("ShapeGrammar.DrawInputOutput - Error: The output topology parameter is not a valid topology. Returning None.")
|
419
|
+
return None
|
420
|
+
|
421
|
+
input_bb = Topology.BoundingBox(input)
|
422
|
+
input_centroid = Topology.Centroid(input_bb)
|
423
|
+
input_d = Topology.Dictionary(input_bb)
|
424
|
+
xmin = Dictionary.ValueAtKey(input_d, "xmin")
|
425
|
+
ymin = Dictionary.ValueAtKey(input_d, "ymin")
|
426
|
+
zmin = Dictionary.ValueAtKey(input_d, "zmin")
|
427
|
+
xmax = Dictionary.ValueAtKey(input_d, "xmax")
|
428
|
+
ymax = Dictionary.ValueAtKey(input_d, "ymax")
|
429
|
+
zmax = Dictionary.ValueAtKey(input_d, "zmax")
|
430
|
+
input_width = xmax-xmin
|
431
|
+
input_length = ymax-ymin
|
432
|
+
input_height = zmax-zmin
|
433
|
+
input_max = max(input_width, input_length, input_height)
|
434
|
+
sf = 1/input_max
|
435
|
+
temp_input = Topology.Translate(input, -Vertex.X(input_centroid), -Vertex.Y(input_centroid), -Vertex.Z(input_centroid))
|
436
|
+
temp_input = Topology.Scale(temp_input, x=sf, y=sf, z=sf)
|
437
|
+
temp_input = Topology.Translate(temp_input, 0.5, 0, 0)
|
438
|
+
|
439
|
+
output_bb = Topology.BoundingBox(output)
|
440
|
+
output_centroid = Topology.Centroid(output_bb)
|
441
|
+
output_d = Topology.Dictionary(output_bb)
|
442
|
+
xmin = Dictionary.ValueAtKey(output_d, "xmin")
|
443
|
+
ymin = Dictionary.ValueAtKey(output_d, "ymin")
|
444
|
+
zmin = Dictionary.ValueAtKey(output_d, "zmin")
|
445
|
+
xmax = Dictionary.ValueAtKey(output_d, "xmax")
|
446
|
+
ymax = Dictionary.ValueAtKey(output_d, "ymax")
|
447
|
+
zmax = Dictionary.ValueAtKey(output_d, "zmax")
|
448
|
+
output_width = xmax-xmin
|
449
|
+
output_length = ymax-ymin
|
450
|
+
output_height = zmax-zmin
|
451
|
+
output_max = max(output_width, output_length, output_height)
|
452
|
+
sf = 1/output_max
|
453
|
+
temp_output = Topology.Translate(output, -Vertex.X(output_centroid), -Vertex.Y(output_centroid), -Vertex.Z(output_centroid))
|
454
|
+
temp_output = Topology.Scale(temp_output, x=sf, y=sf, z=sf)
|
455
|
+
temp_output = Topology.Translate(temp_output, 2.5, 0, 0)
|
456
|
+
|
457
|
+
cyl = Cell.Cylinder(radius=0.04, height=0.4, placement="bottom")
|
458
|
+
cyl=Topology.Rotate(cyl, axis=[0,1,0], angle=90)
|
459
|
+
cyl = Topology.Translate(cyl, 1.25, 0, 0)
|
460
|
+
|
461
|
+
cone = Cell.Cone(baseRadius=0.1, topRadius=0, height=0.15, placement="bottom")
|
462
|
+
cone=Topology.Rotate(cone, axis=[0,1,0], angle=90)
|
463
|
+
cone = Topology.Translate(cone, 1.65, 0, 0)
|
464
|
+
cluster = Cluster.ByTopologies([temp_input, temp_output, cyl, cone])
|
465
|
+
cluster = Topology.Place(cluster, originA=Topology.Centroid(cluster), originB=Vertex.Origin())
|
466
|
+
data = Plotly.DataByTopology(cluster)
|
467
|
+
fig = Plotly.FigureByData(data)
|
468
|
+
return fig
|
469
|
+
|
470
|
+
def FigureByRule(self, rule, silent: bool = False):
|
471
|
+
"""
|
472
|
+
Returns the Plotly figure of the input rule.
|
473
|
+
|
474
|
+
Parameters
|
475
|
+
----------
|
476
|
+
rule : dict
|
477
|
+
The input rule
|
478
|
+
silent : bool, optional
|
479
|
+
If True, suppresses error/warning messages. Default is False.
|
480
|
+
|
481
|
+
Returns
|
482
|
+
-------
|
483
|
+
This function does not return a value
|
484
|
+
"""
|
485
|
+
from topologicpy.Topology import Topology
|
486
|
+
if not isinstance(rule, dict):
|
487
|
+
if not silent:
|
488
|
+
print("ShapeGrammar.DrawRule - Error: The input rule parameter is not a valid rule. Returning None.")
|
489
|
+
return None
|
490
|
+
input = rule["input"]
|
491
|
+
output = self.ApplyRule(input, rule)
|
492
|
+
return self.FigureByInputOutput(input, output)
|
topologicpy/Topology.py
CHANGED
@@ -2118,17 +2118,15 @@ class Topology():
|
|
2118
2118
|
return Topology.ByDXFFile(file, sides=sides)
|
2119
2119
|
|
2120
2120
|
@staticmethod
|
2121
|
-
def ByIFCFile(
|
2121
|
+
def ByIFCFile(ifc_file, includeTypes=[], excludeTypes=[], transferDictionaries=False,
|
2122
2122
|
removeCoplanarFaces=False,
|
2123
|
-
xMin: float = -0.5, yMin: float = -0.5, zMin: float = -0.5,
|
2124
|
-
xMax: float = 0.5, yMax: float = 0.5, zMax: float = 0.5,
|
2125
2123
|
epsilon=0.0001, tolerance=0.0001, silent=False):
|
2126
2124
|
"""
|
2127
2125
|
Create a topology by importing it from an IFC file.
|
2128
2126
|
|
2129
2127
|
Parameters
|
2130
2128
|
----------
|
2131
|
-
|
2129
|
+
ifc_file : file object
|
2132
2130
|
The input IFC file.
|
2133
2131
|
includeTypes : list , optional
|
2134
2132
|
The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
|
@@ -2138,18 +2136,6 @@ class Topology():
|
|
2138
2136
|
If set to True, the dictionaries from the IFC file will be transferred to the topology. Otherwise, they won't. The default is False.
|
2139
2137
|
removeCoplanarFaces : bool , optional
|
2140
2138
|
If set to True, coplanar faces are removed. Otherwise they are not. The default is False.
|
2141
|
-
xMin : float, optional
|
2142
|
-
The desired minimum value to assign for a vertex's X coordinate. The default is -0.5.
|
2143
|
-
yMin : float, optional
|
2144
|
-
The desired minimum value to assign for a vertex's Y coordinate. The default is -0.5.
|
2145
|
-
zMin : float, optional
|
2146
|
-
The desired minimum value to assign for a vertex's Z coordinate. The default is -0.5.
|
2147
|
-
xMax : float, optional
|
2148
|
-
The desired maximum value to assign for a vertex's X coordinate. The default is 0.5.
|
2149
|
-
yMax : float, optional
|
2150
|
-
The desired maximum value to assign for a vertex's Y coordinate. The default is 0.5.
|
2151
|
-
zMax : float, optional
|
2152
|
-
The desired maximum value to assign for a vertex's Z coordinate. The default is 0.5.
|
2153
2139
|
epsilon : float , optional
|
2154
2140
|
The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
|
2155
2141
|
tolerance : float , optional
|
@@ -2162,119 +2148,89 @@ class Topology():
|
|
2162
2148
|
The created list of topologies.
|
2163
2149
|
|
2164
2150
|
"""
|
2151
|
+
import multiprocessing
|
2165
2152
|
import ifcopenshell
|
2166
2153
|
import ifcopenshell.geom
|
2167
|
-
from topologicpy.Vertex import Vertex
|
2168
|
-
from topologicpy.Wire import Wire
|
2169
2154
|
from topologicpy.Face import Face
|
2170
2155
|
from topologicpy.Cluster import Cluster
|
2171
2156
|
from topologicpy.Topology import Topology
|
2172
2157
|
from topologicpy.Dictionary import Dictionary
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
exclude = [t.lower() for t in excludeTypes]
|
2181
|
-
return (not include or is_a in include) and (is_a not in exclude)
|
2182
|
-
|
2183
|
-
with ThreadPoolExecutor() as executor:
|
2184
|
-
products = list(file.by_type("IfcProduct"))
|
2185
|
-
valid_entities = list(executor.map(lambda p: p if is_valid(p) else None, products))
|
2186
|
-
valid_entities = [e for e in valid_entities if e is not None]
|
2187
|
-
|
2158
|
+
|
2159
|
+
# 1 Guard against including and excluding the same types
|
2160
|
+
includeTypes = [s.lower() for s in includeTypes]
|
2161
|
+
excludeTypes = [s.lower() for s in excludeTypes]
|
2162
|
+
if len(includeTypes) > 0 and len(excludeTypes) > 0:
|
2163
|
+
excludeTypes = [s for s in excludeTypes if s not in includeTypes]
|
2164
|
+
# 2 Setup geometry settings
|
2188
2165
|
settings = ifcopenshell.geom.settings()
|
2189
2166
|
settings.set(settings.USE_WORLD_COORDS, True)
|
2167
|
+
# A string representation of the OCC representation
|
2168
|
+
settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
|
2169
|
+
# A string representation of the OCC representation
|
2190
2170
|
settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.SERIALIZED)
|
2171
|
+
|
2172
|
+
# 3 Create iterator
|
2173
|
+
it = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count())
|
2174
|
+
if not it.initialize():
|
2175
|
+
if not silent:
|
2176
|
+
print("Topology.ByIFCFile")
|
2177
|
+
raise RuntimeError("Geometry iterator failed to initialize")
|
2191
2178
|
|
2179
|
+
# 4) Loop over shapes
|
2192
2180
|
topologies = []
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
if
|
2199
|
-
|
2200
|
-
topology = Topology.ByBREPString(
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2181
|
+
|
2182
|
+
while True:
|
2183
|
+
shape = it.get()
|
2184
|
+
element = ifc_file.by_guid(shape.guid)
|
2185
|
+
element_type = element.is_a().lower()
|
2186
|
+
if ((element_type in includeTypes) or (len(includeTypes) == 0)) and ((not element_type in excludeTypes) or (len(excludeTypes) == 0)):
|
2187
|
+
geom = shape.geometry
|
2188
|
+
topology = Topology.ByBREPString(geom.brep_data)
|
2189
|
+
faces = Topology.Faces(topology)
|
2190
|
+
new_faces = []
|
2191
|
+
for face in faces:
|
2192
|
+
eb = Face.ExternalBoundary(face)
|
2193
|
+
ib = Face.InternalBoundaries(face)
|
2194
|
+
f = Face.ByWires(eb, ib)
|
2195
|
+
new_faces.append(f)
|
2196
|
+
topology = Topology.SelfMerge(Cluster.ByTopologies(new_faces))
|
2205
2197
|
if removeCoplanarFaces:
|
2206
2198
|
topology = Topology.RemoveCoplanarFaces(topology, epsilon=epsilon, tolerance=tolerance)
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2227
|
-
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2236
|
-
|
2237
|
-
|
2238
|
-
|
2239
|
-
|
2240
|
-
final_dict = Dictionary.ByMergedDictionaries([final_dict, pset_dict])
|
2241
|
-
|
2242
|
-
topology = Topology.SetDictionary(topology, final_dict)
|
2243
|
-
|
2244
|
-
topologies.append(topology)
|
2245
|
-
|
2246
|
-
final_topologies = []
|
2247
|
-
for topology in topologies:
|
2248
|
-
faces = []
|
2249
|
-
|
2250
|
-
for w in Topology.Wires(topology):
|
2251
|
-
# Skip trivial wires (e.g. edges)
|
2252
|
-
if len(Topology.Vertices(w)) < 3:
|
2253
|
-
continue
|
2254
|
-
|
2255
|
-
# Only attempt face creation if the wire is closed
|
2256
|
-
if Wire.IsClosed(w) and Wire.IsManifold(w):
|
2257
|
-
f = Face.ByWire(w)
|
2258
|
-
if f:
|
2259
|
-
faces.append(f)
|
2260
|
-
continue
|
2261
|
-
|
2262
|
-
# fallback: keep wire
|
2263
|
-
faces.append(w)
|
2199
|
+
if transferDictionaries:
|
2200
|
+
element_dict = {
|
2201
|
+
"TOPOLOGIC_id": str(Topology.UUID(topology)),
|
2202
|
+
"TOPOLOGIC_name": getattr(element, 'Name', "Untitled"),
|
2203
|
+
"TOPOLOGIC_type": Topology.TypeAsString(topology),
|
2204
|
+
"IFC_global_id": getattr(element, 'GlobalId', 0),
|
2205
|
+
"IFC_name": getattr(element, 'Name', "Untitled"),
|
2206
|
+
"IFC_type": element_type
|
2207
|
+
}
|
2208
|
+
|
2209
|
+
# Optionally add property sets
|
2210
|
+
psets = {}
|
2211
|
+
if hasattr(element, 'IsDefinedBy'):
|
2212
|
+
for rel in element.IsDefinedBy:
|
2213
|
+
if rel.is_a('IfcRelDefinesByProperties'):
|
2214
|
+
pdef = rel.RelatingPropertyDefinition
|
2215
|
+
if pdef and pdef.is_a('IfcPropertySet'):
|
2216
|
+
key = f"IFC_{pdef.Name}"
|
2217
|
+
props = {}
|
2218
|
+
for prop in pdef.HasProperties:
|
2219
|
+
if prop.is_a('IfcPropertySingleValue') and prop.NominalValue:
|
2220
|
+
props[f"IFC_{prop.Name}"] = prop.NominalValue.wrappedValue
|
2221
|
+
psets[key] = props
|
2222
|
+
|
2223
|
+
final_dict = Dictionary.ByPythonDictionary(element_dict)
|
2224
|
+
if psets:
|
2225
|
+
pset_dict = Dictionary.ByPythonDictionary(psets)
|
2226
|
+
final_dict = Dictionary.ByMergedDictionaries([final_dict, pset_dict])
|
2227
|
+
|
2228
|
+
topology = Topology.SetDictionary(topology, final_dict)
|
2229
|
+
topologies.append(topology)
|
2230
|
+
if not it.next():
|
2231
|
+
break
|
2264
2232
|
|
2265
|
-
|
2266
|
-
if len(faces) == 1:
|
2267
|
-
final_topology = faces[0]
|
2268
|
-
else:
|
2269
|
-
final_topology = Topology.SelfMerge(Cluster.ByTopologies(faces))
|
2270
|
-
if final_topology == None:
|
2271
|
-
final_topology = Cluster.ByTopologies(faces)
|
2272
|
-
if transferDictionaries:
|
2273
|
-
d = Topology.Dictionary(topology)
|
2274
|
-
final_topology = Topology.SetDictionary(final_topology, d)
|
2275
|
-
|
2276
|
-
final_topologies.append(final_topology)
|
2277
|
-
return final_topologies
|
2233
|
+
return topologies
|
2278
2234
|
|
2279
2235
|
@staticmethod
|
2280
2236
|
def _ByIFCFile_old(file, includeTypes=[], excludeTypes=[], transferDictionaries=False, removeCoplanarFaces=False):
|
@@ -6590,15 +6546,19 @@ class Topology():
|
|
6590
6546
|
return False, None
|
6591
6547
|
# Done with Exclusion Tests.
|
6592
6548
|
|
6593
|
-
|
6594
|
-
|
6595
|
-
|
6596
|
-
largest_faces_a = Topology.LargestFaces(topologyA)
|
6597
|
-
largest_faces_b = Topology.LargestFaces(topologyB)
|
6549
|
+
if Topology.IsInstance(topologyA, "face"):
|
6550
|
+
largest_faces_a = [topologyA]
|
6551
|
+
largest_faces_b = [topologyB]
|
6598
6552
|
else:
|
6599
|
-
|
6600
|
-
|
6601
|
-
|
6553
|
+
faces_a = Topology.Faces(topologyA)
|
6554
|
+
faces_b = Topology.Faces(topologyB)
|
6555
|
+
if len(faces_a) > 0 and len(faces_b) > 0:
|
6556
|
+
largest_faces_a = Topology.LargestFaces(topologyA)
|
6557
|
+
largest_faces_b = Topology.LargestFaces(topologyB)
|
6558
|
+
else:
|
6559
|
+
if not silent:
|
6560
|
+
print("Topology.IsSimilar - Error: The topologies do not have faces. Returning None.")
|
6561
|
+
return False, None
|
6602
6562
|
|
6603
6563
|
# Process largest faces
|
6604
6564
|
for face_a in largest_faces_a:
|
@@ -9949,7 +9909,6 @@ class Topology():
|
|
9949
9909
|
|
9950
9910
|
# Extract translation (last column of the matrix)
|
9951
9911
|
translation = [m[3] for m in matrix[:3]]
|
9952
|
-
print("translation", translation)
|
9953
9912
|
x_translate, y_translate, z_translate = translation
|
9954
9913
|
|
9955
9914
|
# Extract rotation (top-left 3x3 part of the matrix)
|
@@ -9957,7 +9916,6 @@ class Topology():
|
|
9957
9916
|
|
9958
9917
|
# Extract scaling (diagonal of the matrix)
|
9959
9918
|
scaling_factors = [matrix[m][m] for m in [0,1,2]] # scaling is stored in the diagonal of the rotation matrix
|
9960
|
-
print(scaling_factors)
|
9961
9919
|
x_scale, y_scale, z_scale = scaling_factors
|
9962
9920
|
|
9963
9921
|
# Step 1: Apply Scaling
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.8.
|
1
|
+
__version__ = '0.8.30'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.30
|
4
4
|
Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
|
5
5
|
Author-email: Wassim Jabi <wassim.jabi@gmail.com>
|
6
6
|
License: AGPL v3 License
|
@@ -18,20 +18,21 @@ topologicpy/Helper.py,sha256=JdvC30WMrla46mTj5TdwCV_bRv-6y8vK5Bkx0prluy4,29100
|
|
18
18
|
topologicpy/Honeybee.py,sha256=yctkwfdupKnp7bAOjP1Z4YaYpRrWoMEb4gz9Z5zaWwE,21751
|
19
19
|
topologicpy/Matrix.py,sha256=BHGDRkBn1pf5DkRoY8feAhDGHTF3bjFM4jluiEb_A0w,22779
|
20
20
|
topologicpy/Neo4j.py,sha256=vNMaqTWerwr-3luLjYEXNhf8T97aFee6x5sIKBHY73s,22392
|
21
|
-
topologicpy/Plotly.py,sha256=
|
21
|
+
topologicpy/Plotly.py,sha256=FIFahDnWhvbQ--VJjy3lQGl5735w8hDzZNrVkCkpZFE,119455
|
22
22
|
topologicpy/Polyskel.py,sha256=oVfM4lqSMPTjnkHfsRU9VI8Blt6Vf0LVPkD9ebz7Wmw,27082
|
23
23
|
topologicpy/PyG.py,sha256=zvV6jtnol_aFiN6JRoMpYwBVfOU2aFs9gdWSdEo6mtU,109757
|
24
|
+
topologicpy/ShapeGrammar.py,sha256=JwE__VcKum5X3r33WwToEWtpJdFuhzZYyNhU2ob15ss,21194
|
24
25
|
topologicpy/Shell.py,sha256=h8S2nP1e0JtMxeOdAFZVhOYTJWTW8vlZRM5lxK0gu2o,89577
|
25
26
|
topologicpy/Speckle.py,sha256=-eiTqJugd7pHiHpD3pDUcDO6CGhVyPV14HFRzaqEoaw,18187
|
26
27
|
topologicpy/Sun.py,sha256=_VBBAUIDhvpkp72JBZlv7k9qx9jYubm3yM56UZ1Nc6c,36837
|
27
|
-
topologicpy/Topology.py,sha256=
|
28
|
+
topologicpy/Topology.py,sha256=De_-naqB-hBPjcFeDQjbKUS7QzfhVxWJzu-7IMqLojc,476669
|
28
29
|
topologicpy/Vector.py,sha256=mx7fgABdioikPWM9HzXKzmqfx3u_XBcU_jlLD4qK2x8,42407
|
29
30
|
topologicpy/Vertex.py,sha256=UMDhERrLH6b4WOu4pl0UgYzcfp9-NvmASLtKXwetO_4,84687
|
30
31
|
topologicpy/Wire.py,sha256=eRs4PM7h4yU5v6umPh0oBJR4cN8BwsqlVroaFdnvK4w,228499
|
31
32
|
topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
|
32
|
-
topologicpy/version.py,sha256=
|
33
|
-
topologicpy-0.8.
|
34
|
-
topologicpy-0.8.
|
35
|
-
topologicpy-0.8.
|
36
|
-
topologicpy-0.8.
|
37
|
-
topologicpy-0.8.
|
33
|
+
topologicpy/version.py,sha256=jN0mlk8vix45n9If_9Klf_t7fIZ7At3hYPz3_y-xov4,23
|
34
|
+
topologicpy-0.8.30.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
35
|
+
topologicpy-0.8.30.dist-info/METADATA,sha256=RlkwNXmDCcLnsl11dRg0HUVn5D2vEq9BmcH2mJ4zRMU,10535
|
36
|
+
topologicpy-0.8.30.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
37
|
+
topologicpy-0.8.30.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
38
|
+
topologicpy-0.8.30.dist-info/RECORD,,
|
File without changes
|
File without changes
|