topologicpy 0.8.31__py3-none-any.whl → 0.8.33__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/Cell.py +1098 -10
- topologicpy/CellComplex.py +213 -1
- topologicpy/Edge.py +16 -0
- topologicpy/Face.py +319 -54
- topologicpy/Topology.py +7 -7
- topologicpy/Vertex.py +1 -1
- topologicpy/Wire.py +65 -30
- topologicpy/version.py +1 -1
- {topologicpy-0.8.31.dist-info → topologicpy-0.8.33.dist-info}/METADATA +1 -1
- {topologicpy-0.8.31.dist-info → topologicpy-0.8.33.dist-info}/RECORD +13 -13
- {topologicpy-0.8.31.dist-info → topologicpy-0.8.33.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.31.dist-info → topologicpy-0.8.33.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.31.dist-info → topologicpy-0.8.33.dist-info}/top_level.txt +0 -0
topologicpy/CellComplex.py
CHANGED
@@ -48,6 +48,99 @@ except:
|
|
48
48
|
warnings.warn("CellComplex - Error: Could not import scipy.")
|
49
49
|
|
50
50
|
class CellComplex():
|
51
|
+
@staticmethod
|
52
|
+
def AdjacencyDictionary(cellComplex, cellLabelKey: str = None, faceKey: str = None, includeWeights: bool = False, reverse: bool = False, mantissa: int = 6, silent: bool = False):
|
53
|
+
"""
|
54
|
+
Returns the adjacency dictionary of the input Graph.
|
55
|
+
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
cellComplex : topologic_core.CellComplex
|
59
|
+
The input cellComplex.
|
60
|
+
cellLabelKey : str , optional
|
61
|
+
The returned cells are labelled according to the dictionary values stored under this key.
|
62
|
+
If the cellLabelKey does not exist, it will be created and the cells are labelled numerically and stored in the vertex dictionary under this key. The default is None.
|
63
|
+
faceKey : str , optional
|
64
|
+
If set, the faces' dictionaries will be searched for this key to set their weight. If the key is set to "Area" (case insensitive), the area of the shared faces will be used as its weight. If set to None, a weight of 1 will be used. The default is None.
|
65
|
+
includeWeights : bool , optional
|
66
|
+
If set to True, edge weights are included. Otherwise, they are not. The default is False.
|
67
|
+
reverse : bool , optional
|
68
|
+
If set to True, the cells are sorted in reverse order (only if cellLabelKey is set). Otherwise, they are not. The default is False.
|
69
|
+
|
70
|
+
silent : bool , optional
|
71
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
72
|
+
|
73
|
+
Returns
|
74
|
+
-------
|
75
|
+
dict
|
76
|
+
The adjacency dictionary.
|
77
|
+
"""
|
78
|
+
from topologicpy.Face import Face
|
79
|
+
from topologicpy.Dictionary import Dictionary
|
80
|
+
from topologicpy.Topology import Topology
|
81
|
+
from topologicpy.Helper import Helper
|
82
|
+
|
83
|
+
if not Topology.IsInstance(cellComplex, "CellComplex"):
|
84
|
+
if not silent:
|
85
|
+
print("CellComplex.AdjacencyDictionary - Error: The input cellComplex input parameter is not a valid cellComplex. Returning None.")
|
86
|
+
return None
|
87
|
+
if cellLabelKey == None:
|
88
|
+
cellLabelKey = "__label__"
|
89
|
+
if not isinstance(cellLabelKey, str):
|
90
|
+
if not silent:
|
91
|
+
print("CellComplex.AdjacencyDictionary - Error: The input cellLabelKey is not a valid string. Returning None.")
|
92
|
+
return None
|
93
|
+
all_cells = Topology.Cells(cellComplex)
|
94
|
+
labels = []
|
95
|
+
n = max(len(str(len(all_cells))), 3)
|
96
|
+
for i, cell in enumerate(all_cells):
|
97
|
+
d = Topology.Dictionary(cell)
|
98
|
+
value = Dictionary.ValueAtKey(d, cellLabelKey)
|
99
|
+
if value == None:
|
100
|
+
value = str(i+1).zfill(n)
|
101
|
+
if d == None:
|
102
|
+
d = Dictionary.ByKeyValue(cellLabelKey, value)
|
103
|
+
else:
|
104
|
+
d = Dictionary.SetValueAtKey(d, cellLabelKey, value)
|
105
|
+
cell = Topology.SetDictionary(cell, d)
|
106
|
+
labels.append(value)
|
107
|
+
all_cells = Helper.Sort(all_cells, labels)
|
108
|
+
labels.sort()
|
109
|
+
order = len(all_cells)
|
110
|
+
adjDict = {}
|
111
|
+
for i in range(order):
|
112
|
+
cell = all_cells[i]
|
113
|
+
cell_label = labels[i]
|
114
|
+
adjCells = Topology.AdjacentTopologies(cell, hostTopology=cellComplex, topologyType="cell")
|
115
|
+
temp_list = []
|
116
|
+
for adjCell in adjCells:
|
117
|
+
adj_label = Dictionary.ValueAtKey(Topology.Dictionary(adjCell), cellLabelKey)
|
118
|
+
adj_index = labels.index(adj_label)
|
119
|
+
if includeWeights == True:
|
120
|
+
if faceKey == None:
|
121
|
+
weight = 1
|
122
|
+
elif "area" in faceKey.lower():
|
123
|
+
shared_topologies = Topology.SharedTopologies(cell, adjCell)
|
124
|
+
faces = shared_topologies.get("faces", [])
|
125
|
+
weight = sum([Face.Area(face, mantissa=mantissa) for face in faces])
|
126
|
+
else:
|
127
|
+
shared_topologies = Topology.SharedTopologies(cell, adjCell)
|
128
|
+
faces = shared_topologies.get("faces", [])
|
129
|
+
weight = sum([Dictionary.ValueAtKey(Topology.Dictionary(face),faceKey, 0) for face in faces])
|
130
|
+
if not adj_index == None:
|
131
|
+
temp_list.append((adj_label, weight))
|
132
|
+
else:
|
133
|
+
if not adj_index == None:
|
134
|
+
temp_list.append(adj_label)
|
135
|
+
temp_list.sort()
|
136
|
+
adjDict[cell_label] = temp_list
|
137
|
+
if cellLabelKey == "__label__": # This is label we added, so remove it
|
138
|
+
for cell in all_cells:
|
139
|
+
d = Topology.Dictionary(cell)
|
140
|
+
d = Dictionary.RemoveKey(d, cellLabelKey)
|
141
|
+
cell = Topology.SetDictionary(cell, d)
|
142
|
+
return adjDict
|
143
|
+
|
51
144
|
@staticmethod
|
52
145
|
def Box(origin= None,
|
53
146
|
width: float = 1.0, length: float = 1.0, height: float = 1.0,
|
@@ -159,7 +252,7 @@ class CellComplex():
|
|
159
252
|
if transferDictionaries == True:
|
160
253
|
for temp_cell in temp_cells:
|
161
254
|
v = Topology.InternalVertex(temp_cell, tolerance=tolerance)
|
162
|
-
enclosing_cells = Vertex.
|
255
|
+
enclosing_cells = Vertex.EnclosingCells(v, cluster)
|
163
256
|
dictionaries = [Topology.Dictionary(ec) for ec in enclosing_cells]
|
164
257
|
d = Dictionary.ByMergedDictionaries(dictionaries, silent=silent)
|
165
258
|
temp_cell = Topology.SetDictionary(temp_cell, d)
|
@@ -1050,6 +1143,125 @@ class CellComplex():
|
|
1050
1143
|
shells = Topology.Shells(cellComplex)
|
1051
1144
|
return shells
|
1052
1145
|
|
1146
|
+
|
1147
|
+
def _grow_connected_group(seed_idx, group_size, adjacency, visited_global):
|
1148
|
+
"""
|
1149
|
+
Attempts to grow a group of the given size starting from seed_idx using adjacency.
|
1150
|
+
Returns a list of indices if successful, else None.
|
1151
|
+
"""
|
1152
|
+
from collections import deque
|
1153
|
+
import random
|
1154
|
+
|
1155
|
+
group = [seed_idx]
|
1156
|
+
visited = set(group)
|
1157
|
+
queue = deque([seed_idx])
|
1158
|
+
|
1159
|
+
while queue and len(group) < group_size:
|
1160
|
+
current = queue.popleft()
|
1161
|
+
neighbors = adjacency.get(current, [])
|
1162
|
+
random.shuffle(neighbors)
|
1163
|
+
for neighbor in neighbors:
|
1164
|
+
if neighbor not in visited and neighbor not in visited_global:
|
1165
|
+
group.append(neighbor)
|
1166
|
+
visited.add(neighbor)
|
1167
|
+
queue.append(neighbor)
|
1168
|
+
if len(group) >= group_size:
|
1169
|
+
break
|
1170
|
+
|
1171
|
+
return group if len(group) == group_size else None
|
1172
|
+
|
1173
|
+
def SubCombinations(cellComplex,
|
1174
|
+
minCells: int = 2,
|
1175
|
+
maxCells: int = None,
|
1176
|
+
maxCombinations: int = 100,
|
1177
|
+
timeLimit: int = 10,
|
1178
|
+
silent: bool = False):
|
1179
|
+
"""
|
1180
|
+
Creates sub-combination cellComplexes of the input cellComplex. Warning: This is prone to combinatorial explosion.
|
1181
|
+
|
1182
|
+
Parameters
|
1183
|
+
----------
|
1184
|
+
cellComplex : topologic_core.cellComplex
|
1185
|
+
The input cellComplex
|
1186
|
+
minCells : int , optional
|
1187
|
+
The minimum number of cells to include in a combination. The default is 2.
|
1188
|
+
maxCells : int , optional
|
1189
|
+
The maximum number of cells to include in a combinations. The default is None which means the maximum will be set to the number of cells in the cellComplex minus 1.
|
1190
|
+
maxCombinations : int , optional
|
1191
|
+
The maximum number of combinations to create. The default is 100.
|
1192
|
+
timeLimit : int , optional
|
1193
|
+
The time limit in seconds. The default is 10 seconds. Note that this time limit only applies to creating the combination indices and not the actual CellComplexes.
|
1194
|
+
tolerance : float , optional
|
1195
|
+
The tolerance for computing if the input vertex is external to the input topology. The default is 0.0001.
|
1196
|
+
silent : bool , optional
|
1197
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1198
|
+
|
1199
|
+
Returns
|
1200
|
+
-------
|
1201
|
+
list
|
1202
|
+
The list of created CellComplex sub-combinations.
|
1203
|
+
|
1204
|
+
"""
|
1205
|
+
from topologicpy.Cluster import Cluster
|
1206
|
+
from topologicpy.Topology import Topology
|
1207
|
+
from topologicpy.Dictionary import Dictionary
|
1208
|
+
import random
|
1209
|
+
import time
|
1210
|
+
|
1211
|
+
if not Topology.IsInstance(cellComplex, "CellComplex"):
|
1212
|
+
if not silent:
|
1213
|
+
print("CellComplex.SubCombinations - Error: The cellComplex input parameter is not a valid cellComplex. Returning None.")
|
1214
|
+
return None
|
1215
|
+
|
1216
|
+
|
1217
|
+
start_time = time.time()
|
1218
|
+
all_cells = CellComplex.Cells(cellComplex)
|
1219
|
+
num_cells = len(all_cells)
|
1220
|
+
indices = list(range(num_cells))
|
1221
|
+
cell_label_key = "index"
|
1222
|
+
# 1. Assign a unique index to each cell's dictionary
|
1223
|
+
all_cells = [Topology.SetDictionary(cell, Dictionary.ByKeyValue(cell_label_key, i)) for i, cell in enumerate(all_cells)]
|
1224
|
+
if maxCells == None:
|
1225
|
+
maxCells = len(all_cells) - 1
|
1226
|
+
# 2. Allocate counts per group size
|
1227
|
+
group_sizes = list(range(minCells, maxCells + 1))
|
1228
|
+
combinations_per_size = maxCombinations // len(group_sizes)
|
1229
|
+
|
1230
|
+
# 3. Build adjacency dict
|
1231
|
+
adjacency = CellComplex.AdjacencyDictionary(cellComplex, cellLabelKey=cell_label_key, faceKey=None, includeWeights=False)
|
1232
|
+
|
1233
|
+
results = []
|
1234
|
+
seen_groups = set() # sets of sorted indices
|
1235
|
+
|
1236
|
+
# 4. Start from longest group size
|
1237
|
+
for group_size in reversed(group_sizes):
|
1238
|
+
remaining = combinations_per_size
|
1239
|
+
tries = 0
|
1240
|
+
max_tries = combinations_per_size * 10 # fallback guard
|
1241
|
+
while remaining > 0 and time.time() - start_time < timeLimit and tries < max_tries:
|
1242
|
+
random.shuffle(indices)
|
1243
|
+
for seed in indices:
|
1244
|
+
if time.time() - start_time > timeLimit:
|
1245
|
+
break
|
1246
|
+
group = CellComplex._grow_connected_group(seed, group_size, adjacency, visited_global=seen_groups)
|
1247
|
+
if group:
|
1248
|
+
key = tuple(sorted(group))
|
1249
|
+
if key not in seen_groups:
|
1250
|
+
tries += 1
|
1251
|
+
seen_groups.add(key)
|
1252
|
+
results.append(group)
|
1253
|
+
remaining -= 1
|
1254
|
+
if remaining <= 0 or tries >= max_tries:
|
1255
|
+
break
|
1256
|
+
# 5. Build CellComplex SubCombinations
|
1257
|
+
return_combinations = []
|
1258
|
+
for i, result in enumerate(reversed(results)):
|
1259
|
+
cells = [all_cells[i] for i in result]
|
1260
|
+
combination = Topology.SelfMerge(Cluster.ByTopologies(cells))
|
1261
|
+
if Topology.IsInstance(combination, "CellComplex"):
|
1262
|
+
return_combinations.append(combination)
|
1263
|
+
return return_combinations
|
1264
|
+
|
1053
1265
|
@staticmethod
|
1054
1266
|
def Tetrahedron(origin = None, length: float = 1, depth: int = 1, direction=[0,0,1], placement="center", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
1055
1267
|
"""
|
topologicpy/Edge.py
CHANGED
@@ -306,29 +306,45 @@ class Edge():
|
|
306
306
|
"""
|
307
307
|
from topologicpy.Vertex import Vertex
|
308
308
|
from topologicpy.Topology import Topology
|
309
|
+
import inspect
|
309
310
|
|
310
311
|
edge = None
|
311
312
|
if not Topology.IsInstance(vertexA, "Vertex"):
|
312
313
|
if not silent:
|
313
314
|
print("Edge.ByStartVertexEndVertex - Error: The input vertexA parameter is not a valid topologic vertex. Returning None.")
|
315
|
+
curframe = inspect.currentframe()
|
316
|
+
calframe = inspect.getouterframes(curframe, 2)
|
317
|
+
print('caller name:', calframe[1][3])
|
314
318
|
return None
|
315
319
|
if not Topology.IsInstance(vertexB, "Vertex"):
|
316
320
|
if not silent:
|
317
321
|
print("Edge.ByStartVertexEndVertex - Error: The input vertexB parameter is not a valid topologic vertex. Returning None.")
|
322
|
+
curframe = inspect.currentframe()
|
323
|
+
calframe = inspect.getouterframes(curframe, 2)
|
324
|
+
print('caller name:', calframe[1][3])
|
318
325
|
return None
|
319
326
|
if Topology.IsSame(vertexA, vertexB):
|
320
327
|
if not silent:
|
321
328
|
print("Edge.ByStartVertexEndVertex - Error: The input vertexA and vertexB parameters are the same vertex. Returning None.")
|
329
|
+
curframe = inspect.currentframe()
|
330
|
+
calframe = inspect.getouterframes(curframe, 2)
|
331
|
+
print('caller name:', calframe[1][3])
|
322
332
|
return None
|
323
333
|
if Vertex.Distance(vertexA, vertexB) <= tolerance:
|
324
334
|
if not silent:
|
325
335
|
print("Edge.ByStartVertexEndVertex - Error: The distance between the input vertexA and vertexB parameters is less than the input tolerance. Returning None.")
|
336
|
+
curframe = inspect.currentframe()
|
337
|
+
calframe = inspect.getouterframes(curframe, 2)
|
338
|
+
print('caller name:', calframe[1][3])
|
326
339
|
return None
|
327
340
|
try:
|
328
341
|
edge = topologic.Edge.ByStartVertexEndVertex(vertexA, vertexB) # Hook to Core
|
329
342
|
except:
|
330
343
|
if not silent:
|
331
344
|
print("Edge.ByStartVertexEndVertex - Error: Could not create an edge. Returning None.")
|
345
|
+
curframe = inspect.currentframe()
|
346
|
+
calframe = inspect.getouterframes(curframe, 2)
|
347
|
+
print('caller name:', calframe[1][3])
|
332
348
|
edge = None
|
333
349
|
return edge
|
334
350
|
|