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 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
- label = str(Dictionary.ValueAtKey(d, key=labelKey)) or ""
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=label,
731
- hoverinfo='text')
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(file, includeTypes=[], excludeTypes=[], transferDictionaries=False,
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
- file : file object
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
- from concurrent.futures import ThreadPoolExecutor
2174
- import random
2175
-
2176
- # Early filtering in parallel (safe)
2177
- def is_valid(product):
2178
- is_a = product.is_a().lower()
2179
- include = [t.lower() for t in includeTypes]
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
- for entity in valid_entities:
2194
- if not hasattr(entity, "Representation") or not entity.Representation:
2195
- continue
2196
- shape = ifcopenshell.geom.create_shape(settings, entity)
2197
- topology = None
2198
- if hasattr(shape.geometry, 'brep_data'):
2199
- brep_string = shape.geometry.brep_data
2200
- topology = Topology.ByBREPString(brep_string)
2201
- if not topology:
2202
- if not silent:
2203
- print(f"Topology.ByIFCFile - Warning: Could not convert entity {getattr(entity, 'GlobalId', 0)} to a topology. Skipping.")
2204
- continue
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
- else:
2208
- if not silent:
2209
- print(f"Topology.ByIFCFile - Warning: Entity {getattr(entity, 'GlobalId', 0)} does not have a BREP. Skipping.")
2210
- continue
2211
-
2212
-
2213
- if transferDictionaries:
2214
- entity_dict = {
2215
- "TOPOLOGIC_id": str(Topology.UUID(topology)),
2216
- "TOPOLOGIC_name": getattr(entity, 'Name', "Untitled"),
2217
- "TOPOLOGIC_type": Topology.TypeAsString(topology),
2218
- "IFC_global_id": getattr(entity, 'GlobalId', 0),
2219
- "IFC_name": getattr(entity, 'Name', "Untitled"),
2220
- "IFC_type": entity.is_a()
2221
- }
2222
-
2223
- # Optionally add property sets
2224
- psets = {}
2225
- if hasattr(entity, 'IsDefinedBy'):
2226
- for rel in entity.IsDefinedBy:
2227
- if rel.is_a('IfcRelDefinesByProperties'):
2228
- pdef = rel.RelatingPropertyDefinition
2229
- if pdef and pdef.is_a('IfcPropertySet'):
2230
- key = f"IFC_{pdef.Name}"
2231
- props = {}
2232
- for prop in pdef.HasProperties:
2233
- if prop.is_a('IfcPropertySingleValue') and prop.NominalValue:
2234
- props[f"IFC_{prop.Name}"] = prop.NominalValue.wrappedValue
2235
- psets[key] = props
2236
-
2237
- final_dict = Dictionary.ByPythonDictionary(entity_dict)
2238
- if psets:
2239
- pset_dict = Dictionary.ByPythonDictionary(psets)
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
- # Avoid unnecessary Cluster/SelfMerge if there's only one face
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
- faces_a = Topology.Faces(topologyA)
6594
- faces_b = Topology.Faces(topologyB)
6595
- if len(faces_a) > 0 and len(faces_b) > 0:
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
- if not silent:
6600
- print("Topology.IsSimilar - Error: The topologies do not have faces. Returning None.")
6601
- return False, None
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.28'
1
+ __version__ = '0.8.30'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.28
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=s0fC_eNsgJN8zkkY_EomZi6jnKCIdtrVYsgnS51MWkU,119271
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=3wTW_C3lquA7lc5sLMxxoBj53Ygc--6hLl5LGvnv3NI,478412
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=YRGDu9QPaNPQlZcx7MJLLUrv2aIEpppZ8LAIiyZvW00,23
33
- topologicpy-0.8.28.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
34
- topologicpy-0.8.28.dist-info/METADATA,sha256=W4Rje6xhqAy7LxQUXrv0Q5yY97BFPOKC0iWuZDqjjJM,10535
35
- topologicpy-0.8.28.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
36
- topologicpy-0.8.28.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
37
- topologicpy-0.8.28.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5