topologicpy 0.8.33__py3-none-any.whl → 0.8.35__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/CellComplex.py +1 -186
- topologicpy/Dictionary.py +119 -0
- topologicpy/Graph.py +2 -7
- topologicpy/Helper.py +52 -0
- topologicpy/Shell.py +119 -92
- topologicpy/Topology.py +189 -11
- topologicpy/Wire.py +1 -1
- topologicpy/version.py +1 -1
- {topologicpy-0.8.33.dist-info → topologicpy-0.8.35.dist-info}/METADATA +1 -1
- {topologicpy-0.8.33.dist-info → topologicpy-0.8.35.dist-info}/RECORD +13 -13
- {topologicpy-0.8.33.dist-info → topologicpy-0.8.35.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.33.dist-info → topologicpy-0.8.35.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.33.dist-info → topologicpy-0.8.35.dist-info}/top_level.txt +0 -0
topologicpy/CellComplex.py
CHANGED
@@ -48,99 +48,6 @@ 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
|
-
|
144
51
|
@staticmethod
|
145
52
|
def Box(origin= None,
|
146
53
|
width: float = 1.0, length: float = 1.0, height: float = 1.0,
|
@@ -1143,7 +1050,7 @@ class CellComplex():
|
|
1143
1050
|
shells = Topology.Shells(cellComplex)
|
1144
1051
|
return shells
|
1145
1052
|
|
1146
|
-
|
1053
|
+
@staticmethod
|
1147
1054
|
def _grow_connected_group(seed_idx, group_size, adjacency, visited_global):
|
1148
1055
|
"""
|
1149
1056
|
Attempts to grow a group of the given size starting from seed_idx using adjacency.
|
@@ -1170,98 +1077,6 @@ class CellComplex():
|
|
1170
1077
|
|
1171
1078
|
return group if len(group) == group_size else None
|
1172
1079
|
|
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
|
-
|
1265
1080
|
@staticmethod
|
1266
1081
|
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):
|
1267
1082
|
"""
|
topologicpy/Dictionary.py
CHANGED
@@ -46,6 +46,125 @@ class Dictionary():
|
|
46
46
|
dictionaries.append(Dictionary.ByKeysValues(keys, values))
|
47
47
|
return dictionaries
|
48
48
|
'''
|
49
|
+
@staticmethod
|
50
|
+
def AdjacencyDictionary(topology, subTopologyType: str = None, labelKey: str = None, weightKey: str = None, includeWeights: bool = False, mantissa: int = 6, silent: bool = False):
|
51
|
+
"""
|
52
|
+
Returns the adjacency dictionary of the input Shell.
|
53
|
+
|
54
|
+
Parameters
|
55
|
+
----------
|
56
|
+
topology : topologic_core.Topology
|
57
|
+
The input topology.
|
58
|
+
subTopologyType : str , optional
|
59
|
+
The type of subTopology on which to base the adjacency dictionary.
|
60
|
+
labelKey : str , optional
|
61
|
+
The returned subTopologies are labelled according to the dictionary values stored under this key.
|
62
|
+
If the labelKey does not exist, it will be created and the subTopologies are labelled numerically and stored in the subTopologies' dictionary under this key. The default is None.
|
63
|
+
weightKey : str , optional
|
64
|
+
If set, the sharedTopologies' dictionaries will be searched for this key to set their weight. If the key is set to "Area" or "Length" (case insensitive), the area of shared faces or the length of the shared edges 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
|
+
mantissa : int , optional
|
68
|
+
The desired length of the mantissa. The default is 6.
|
69
|
+
silent : bool , optional
|
70
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
71
|
+
|
72
|
+
Returns
|
73
|
+
-------
|
74
|
+
dict
|
75
|
+
The adjacency dictionary.
|
76
|
+
"""
|
77
|
+
from topologicpy.Edge import Edge
|
78
|
+
from topologicpy.Dictionary import Dictionary
|
79
|
+
from topologicpy.Topology import Topology
|
80
|
+
from topologicpy.Graph import Graph
|
81
|
+
from topologicpy.Helper import Helper
|
82
|
+
|
83
|
+
if not Topology.IsInstance(topology, "Topology") and not Topology.IsInstance(topology, "Graph"):
|
84
|
+
if not silent:
|
85
|
+
print("Dictionary.AdjacencyDictionary - Error: The input topology input parameter is not a valid topology. Returning None.")
|
86
|
+
return None
|
87
|
+
# Special Case for Graphs
|
88
|
+
|
89
|
+
if Topology.IsInstance(topology, "Graph"):
|
90
|
+
return Graph.AdjacencyDictionary(topology, vertexLabelKey=labelKey, edgeKey=weightKey, includeWeights=includeWeights, mantissa=mantissa)
|
91
|
+
if labelKey == None:
|
92
|
+
labelKey = "__label__"
|
93
|
+
if not isinstance(labelKey, str):
|
94
|
+
if not silent:
|
95
|
+
print("Dictionary.AdjacencyDictionary - Error: The input labelKey is not a valid string. Returning None.")
|
96
|
+
return None
|
97
|
+
if Topology.IsInstance(topology, "cellcomplex"):
|
98
|
+
if subTopologyType == None:
|
99
|
+
subTopologyType = "cell"
|
100
|
+
all_subtopologies = Topology.SubTopologies(topology, subTopologyType=subTopologyType, silent=silent)
|
101
|
+
elif Topology.IsInstance(topology, "cell") or Topology.IsInstance(topology, "shell"):
|
102
|
+
if subTopologyType == None:
|
103
|
+
subTopologyType = "face"
|
104
|
+
all_subtopologies = Topology.SubTopologies(topology, subTopologyType=subTopologyType, silent=silent)
|
105
|
+
elif Topology.IsInstance(topology, "face") or Topology.IsInstance(topology, "wire"):
|
106
|
+
if subTopologyType == None:
|
107
|
+
subTopologyType = "edge"
|
108
|
+
all_subtopologies = Topology.SubTopologies(topology, subTopologyType=subTopologyType, silent=silent)
|
109
|
+
labels = []
|
110
|
+
n = max(len(str(len(all_subtopologies))), 3)
|
111
|
+
for i, subtopology in enumerate(all_subtopologies):
|
112
|
+
d = Topology.Dictionary(subtopology)
|
113
|
+
value = Dictionary.ValueAtKey(d, labelKey)
|
114
|
+
if value == None:
|
115
|
+
value = str(i+1).zfill(n)
|
116
|
+
if d == None:
|
117
|
+
d = Dictionary.ByKeyValue(labelKey, value)
|
118
|
+
else:
|
119
|
+
d = Dictionary.SetValueAtKey(d, labelKey, value)
|
120
|
+
subtopology = Topology.SetDictionary(subtopology, d)
|
121
|
+
labels.append(value)
|
122
|
+
all_subtopologies = Helper.Sort(all_subtopologies, labels)
|
123
|
+
labels.sort()
|
124
|
+
order = len(all_subtopologies)
|
125
|
+
adjDict = {}
|
126
|
+
for i in range(order):
|
127
|
+
subtopology = all_subtopologies[i]
|
128
|
+
subt_label = labels[i]
|
129
|
+
adjacent_topologies = Topology.AdjacentTopologies(subtopology, hostTopology=topology, topologyType=subTopologyType)
|
130
|
+
temp_list = []
|
131
|
+
for adj_topology in adjacent_topologies:
|
132
|
+
adj_label = Dictionary.ValueAtKey(Topology.Dictionary(adj_topology), labelKey)
|
133
|
+
adj_index = labels.index(adj_label)
|
134
|
+
if includeWeights == True:
|
135
|
+
if weightKey == None:
|
136
|
+
weight = 1
|
137
|
+
elif "length" in weightKey.lower():
|
138
|
+
shared_topologies = Topology.SharedTopologies(subtopology, adj_topology)
|
139
|
+
edges = shared_topologies.get("edges", [])
|
140
|
+
weight = sum([Edge.Length(edge, mantissa=mantissa) for edge in edges])
|
141
|
+
elif "area" in weightKey.lower():
|
142
|
+
shared_topologies = Topology.SharedTopologies(subtopology, adj_topology)
|
143
|
+
faces = shared_topologies.get("faces", [])
|
144
|
+
weight = sum([Edge.Length(edge, mantissa=mantissa) for face in faces])
|
145
|
+
else:
|
146
|
+
shared_topologies = Topology.SharedTopologies(subtopology, adj_topology)
|
147
|
+
vertices = shared_topologies.get("vertices", [])
|
148
|
+
edges = shared_topologies.get("edges", [])
|
149
|
+
wires = shared_topologies.get("wires", [])
|
150
|
+
faces = shared_topologies.get("faces", [])
|
151
|
+
everything = vertices+edges+wires+faces
|
152
|
+
weight = sum([Dictionary.ValueAtKey(Topology.Dictionary(x),weightKey, 0) for x in everything])
|
153
|
+
weight = round(weight, mantissa)
|
154
|
+
if not adj_index == None:
|
155
|
+
temp_list.append((adj_label, weight))
|
156
|
+
else:
|
157
|
+
if not adj_index == None:
|
158
|
+
temp_list.append(adj_label)
|
159
|
+
temp_list.sort()
|
160
|
+
adjDict[subt_label] = temp_list
|
161
|
+
if labelKey == "__label__": # This is label we added, so remove it
|
162
|
+
for subtopology in all_subtopologies:
|
163
|
+
d = Topology.Dictionary(subtopology)
|
164
|
+
d = Dictionary.RemoveKey(d, labelKey)
|
165
|
+
subtopology = Topology.SetDictionary(subtopology, d)
|
166
|
+
return adjDict
|
167
|
+
|
49
168
|
@staticmethod
|
50
169
|
def ByKeyValue(key, value):
|
51
170
|
"""
|
topologicpy/Graph.py
CHANGED
@@ -523,7 +523,7 @@ class Graph:
|
|
523
523
|
_ = graph.AddVertices(vertices, tolerance) # Hook to Core
|
524
524
|
return graph
|
525
525
|
|
526
|
-
def AdjacencyDictionary(graph, vertexLabelKey: str = None, edgeKey: str = "Length", includeWeights: bool = False,
|
526
|
+
def AdjacencyDictionary(graph, vertexLabelKey: str = None, edgeKey: str = "Length", includeWeights: bool = False, mantissa: int = 6):
|
527
527
|
"""
|
528
528
|
Returns the adjacency dictionary of the input Graph.
|
529
529
|
|
@@ -537,9 +537,7 @@ class Graph:
|
|
537
537
|
edgeKey : str , optional
|
538
538
|
If set, the edges' dictionaries will be searched for this key to set their weight. If the key is set to "length" (case insensitive), the length of the edge will be used as its weight. If set to None, a weight of 1 will be used. The default is "Length".
|
539
539
|
includeWeights : bool , optional
|
540
|
-
If set to True, edge weights are included. Otherwise, they are not. The default is False.
|
541
|
-
reverse : bool , optional
|
542
|
-
If set to True, the vertices are sorted in reverse order (only if vertexKey is set). Otherwise, they are not. The default is False.
|
540
|
+
If set to True, edge weights are included. Otherwise, they are not. The default is False.
|
543
541
|
mantissa : int , optional
|
544
542
|
The desired length of the mantissa. The default is 6.
|
545
543
|
|
@@ -578,9 +576,6 @@ class Graph:
|
|
578
576
|
labels.append(value)
|
579
577
|
vertices = Helper.Sort(vertices, labels)
|
580
578
|
labels.sort()
|
581
|
-
if reverse == True:
|
582
|
-
vertices.reverse()
|
583
|
-
labels.reverse()
|
584
579
|
order = len(vertices)
|
585
580
|
adjDict = {}
|
586
581
|
for i in range(order):
|
topologicpy/Helper.py
CHANGED
@@ -286,6 +286,58 @@ class Helper:
|
|
286
286
|
flat_list = flat_list + Helper.Flatten(item)
|
287
287
|
return flat_list
|
288
288
|
|
289
|
+
@staticmethod
|
290
|
+
def Grow(seed_idx, group_size, adjacency, visited_global):
|
291
|
+
"""
|
292
|
+
Attempts to grow a spatially connected group of a specified size starting from a given seed index.
|
293
|
+
|
294
|
+
This method uses a breadth-first search strategy to explore neighboring indices from the seed index,
|
295
|
+
guided by the provided adjacency dictionary. It avoids reusing indices that are globally visited.
|
296
|
+
The growth continues until the desired group size is reached or no further expansion is possible.
|
297
|
+
|
298
|
+
Parameters
|
299
|
+
----------
|
300
|
+
seed_idx : int
|
301
|
+
The index from which to start growing the group.
|
302
|
+
group_size : int
|
303
|
+
The target size of the group to be grown.
|
304
|
+
adjacency : dict
|
305
|
+
A dictionary mapping each index to a list of adjacent indices. This defines the connectivity.
|
306
|
+
visited_global : set
|
307
|
+
A set of indices that have already been used in previously grown groups and should be avoided.
|
308
|
+
|
309
|
+
Returns
|
310
|
+
-------
|
311
|
+
list[int] or None
|
312
|
+
A list of indices representing a connected group of the specified size if successful, otherwise None.
|
313
|
+
|
314
|
+
Notes
|
315
|
+
-----
|
316
|
+
This method is intended for internal use in functions that generate connected subgroups
|
317
|
+
of spatial elements (e.g., cells) based on adjacency. The result may vary between runs due to random shuffling
|
318
|
+
of neighbor order to diversify outputs.
|
319
|
+
"""
|
320
|
+
from collections import deque
|
321
|
+
import random
|
322
|
+
|
323
|
+
group = [seed_idx]
|
324
|
+
visited = set(group)
|
325
|
+
queue = deque([seed_idx])
|
326
|
+
|
327
|
+
while queue and len(group) < group_size:
|
328
|
+
current = queue.popleft()
|
329
|
+
neighbors = adjacency.get(current, [])
|
330
|
+
random.shuffle(neighbors)
|
331
|
+
for neighbor in neighbors:
|
332
|
+
if neighbor not in visited and neighbor not in visited_global:
|
333
|
+
group.append(neighbor)
|
334
|
+
visited.add(neighbor)
|
335
|
+
queue.append(neighbor)
|
336
|
+
if len(group) >= group_size:
|
337
|
+
break
|
338
|
+
|
339
|
+
return group if len(group) == group_size else None
|
340
|
+
|
289
341
|
@staticmethod
|
290
342
|
def Iterate(listA):
|
291
343
|
"""
|
topologicpy/Shell.py
CHANGED
@@ -739,42 +739,6 @@ class Shell():
|
|
739
739
|
_ = shell.Faces(None, faces)
|
740
740
|
return faces
|
741
741
|
|
742
|
-
@staticmethod
|
743
|
-
def IsOnBoundary(shell, vertex, tolerance: float = 0.0001) -> bool:
|
744
|
-
"""
|
745
|
-
Returns True if the input vertex is on the boundary of the input shell. Returns False otherwise. On the boundary is defined as being on the boundary of one of the shell's external or internal boundaries
|
746
|
-
|
747
|
-
Parameters
|
748
|
-
----------
|
749
|
-
shell : topologic_core.Shell
|
750
|
-
The input shell.
|
751
|
-
vertex : topologic_core.Vertex
|
752
|
-
The input vertex.
|
753
|
-
tolerance : float , optional
|
754
|
-
The desired tolerance. The default is 0.0001.
|
755
|
-
|
756
|
-
Returns
|
757
|
-
-------
|
758
|
-
bool
|
759
|
-
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
760
|
-
|
761
|
-
"""
|
762
|
-
from topologicpy.Vertex import Vertex
|
763
|
-
from topologicpy.Topology import Topology
|
764
|
-
|
765
|
-
if not Topology.IsInstance(shell, "Shell"):
|
766
|
-
return None
|
767
|
-
if not Topology.IsInstance(vertex, "Vertex"):
|
768
|
-
return None
|
769
|
-
boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
|
770
|
-
if Vertex.IsInternal(vertex, boundary, tolerance=tolerance):
|
771
|
-
return True
|
772
|
-
internal_boundaries = Shell.InternalBoundaries(shell, tolerance=tolerance)
|
773
|
-
for ib in internal_boundaries:
|
774
|
-
if Vertex.IsInternal(vertex, ib, tolerance=tolerance):
|
775
|
-
return True
|
776
|
-
return False
|
777
|
-
|
778
742
|
@staticmethod
|
779
743
|
def HyperbolicParaboloidRectangularDomain(origin= None,
|
780
744
|
llVertex= None,
|
@@ -1097,6 +1061,41 @@ class Shell():
|
|
1097
1061
|
"""
|
1098
1062
|
return shell.IsClosed()
|
1099
1063
|
|
1064
|
+
@staticmethod
|
1065
|
+
def IsOnBoundary(shell, vertex, tolerance: float = 0.0001) -> bool:
|
1066
|
+
"""
|
1067
|
+
Returns True if the input vertex is on the boundary of the input shell. Returns False otherwise. On the boundary is defined as being on the boundary of one of the shell's external or internal boundaries
|
1068
|
+
|
1069
|
+
Parameters
|
1070
|
+
----------
|
1071
|
+
shell : topologic_core.Shell
|
1072
|
+
The input shell.
|
1073
|
+
vertex : topologic_core.Vertex
|
1074
|
+
The input vertex.
|
1075
|
+
tolerance : float , optional
|
1076
|
+
The desired tolerance. The default is 0.0001.
|
1077
|
+
|
1078
|
+
Returns
|
1079
|
+
-------
|
1080
|
+
bool
|
1081
|
+
Returns True if the input vertex is inside the input shell. Returns False otherwise.
|
1082
|
+
|
1083
|
+
"""
|
1084
|
+
from topologicpy.Vertex import Vertex
|
1085
|
+
from topologicpy.Topology import Topology
|
1086
|
+
|
1087
|
+
if not Topology.IsInstance(shell, "Shell"):
|
1088
|
+
return None
|
1089
|
+
if not Topology.IsInstance(vertex, "Vertex"):
|
1090
|
+
return None
|
1091
|
+
boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
|
1092
|
+
if Vertex.IsInternal(vertex, boundary, tolerance=tolerance):
|
1093
|
+
return True
|
1094
|
+
internal_boundaries = Shell.InternalBoundaries(shell, tolerance=tolerance)
|
1095
|
+
for ib in internal_boundaries:
|
1096
|
+
if Vertex.IsInternal(vertex, ib, tolerance=tolerance):
|
1097
|
+
return True
|
1098
|
+
return False
|
1100
1099
|
|
1101
1100
|
@staticmethod
|
1102
1101
|
def Paraboloid(origin= None, focalLength=0.125, width: float = 1, length: float = 1, uSides: int = 16, vSides: int = 16,
|
@@ -1644,62 +1643,6 @@ class Shell():
|
|
1644
1643
|
else:
|
1645
1644
|
return None
|
1646
1645
|
|
1647
|
-
def Skeleton(face, tolerance: float = 0.001):
|
1648
|
-
"""
|
1649
|
-
Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1650
|
-
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1651
|
-
|
1652
|
-
Parameters
|
1653
|
-
----------
|
1654
|
-
face : topologic_core.Face
|
1655
|
-
The input face.
|
1656
|
-
tolerance : float , optional
|
1657
|
-
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
1658
|
-
|
1659
|
-
Returns
|
1660
|
-
-------
|
1661
|
-
topologic_core.Shell
|
1662
|
-
The created straight skeleton.
|
1663
|
-
|
1664
|
-
"""
|
1665
|
-
from topologicpy.Wire import Wire
|
1666
|
-
from topologicpy.Face import Face
|
1667
|
-
from topologicpy.Topology import Topology
|
1668
|
-
import topologic_core as topologic
|
1669
|
-
import math
|
1670
|
-
|
1671
|
-
if not Topology.IsInstance(face, "Face"):
|
1672
|
-
return None
|
1673
|
-
roof = Wire.Skeleton(face, tolerance=tolerance)
|
1674
|
-
if not (Topology.IsInstance(roof, "Wire") or Topology.IsInstance(roof, "Cluster")):
|
1675
|
-
print("Shell.Skeleton - Error: Could not create base skeleton wire. Returning None.")
|
1676
|
-
return None
|
1677
|
-
br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
|
1678
|
-
if not Topology.IsInstance(br, "Wire"):
|
1679
|
-
print("Shell.Skeleton - Error: Could not create a bounding rectangle wire. Returning None.")
|
1680
|
-
return None
|
1681
|
-
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
|
1682
|
-
bf = Face.ByWire(br, tolerance=tolerance)
|
1683
|
-
if not Topology.IsInstance(bf, "Face"):
|
1684
|
-
print("Shell.Skeleton - Error: Could not create a bounding rectangle face. Returning None.")
|
1685
|
-
return None
|
1686
|
-
large_shell = Topology.Boolean(bf, roof, operation="slice", tolerance=tolerance)
|
1687
|
-
if not large_shell:
|
1688
|
-
return None
|
1689
|
-
faces = Topology.Faces(large_shell)
|
1690
|
-
if not faces:
|
1691
|
-
return None
|
1692
|
-
final_faces = []
|
1693
|
-
for f in faces:
|
1694
|
-
internalBoundaries = Face.InternalBoundaries(f)
|
1695
|
-
if len(internalBoundaries) == 0:
|
1696
|
-
final_faces.append(f)
|
1697
|
-
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1698
|
-
if not Topology.IsInstance(shell, "Shell"):
|
1699
|
-
print("Shell.Skeleton - Error: Could not create shell. Returning None.")
|
1700
|
-
return None
|
1701
|
-
return shell
|
1702
|
-
|
1703
1646
|
@staticmethod
|
1704
1647
|
def Simplify(shell, simplifyBoundary: bool = True, mantissa: int = 6, tolerance: float = 0.0001):
|
1705
1648
|
"""
|
@@ -1846,6 +1789,63 @@ class Shell():
|
|
1846
1789
|
final_result = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1847
1790
|
return final_result
|
1848
1791
|
|
1792
|
+
@staticmethod
|
1793
|
+
def Skeleton(face, tolerance: float = 0.001):
|
1794
|
+
"""
|
1795
|
+
Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
|
1796
|
+
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
|
1797
|
+
|
1798
|
+
Parameters
|
1799
|
+
----------
|
1800
|
+
face : topologic_core.Face
|
1801
|
+
The input face.
|
1802
|
+
tolerance : float , optional
|
1803
|
+
The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
|
1804
|
+
|
1805
|
+
Returns
|
1806
|
+
-------
|
1807
|
+
topologic_core.Shell
|
1808
|
+
The created straight skeleton.
|
1809
|
+
|
1810
|
+
"""
|
1811
|
+
from topologicpy.Wire import Wire
|
1812
|
+
from topologicpy.Face import Face
|
1813
|
+
from topologicpy.Topology import Topology
|
1814
|
+
import topologic_core as topologic
|
1815
|
+
import math
|
1816
|
+
|
1817
|
+
if not Topology.IsInstance(face, "Face"):
|
1818
|
+
return None
|
1819
|
+
roof = Wire.Skeleton(face, tolerance=tolerance)
|
1820
|
+
if not (Topology.IsInstance(roof, "Wire") or Topology.IsInstance(roof, "Cluster")):
|
1821
|
+
print("Shell.Skeleton - Error: Could not create base skeleton wire. Returning None.")
|
1822
|
+
return None
|
1823
|
+
br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
|
1824
|
+
if not Topology.IsInstance(br, "Wire"):
|
1825
|
+
print("Shell.Skeleton - Error: Could not create a bounding rectangle wire. Returning None.")
|
1826
|
+
return None
|
1827
|
+
br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
|
1828
|
+
bf = Face.ByWire(br, tolerance=tolerance)
|
1829
|
+
if not Topology.IsInstance(bf, "Face"):
|
1830
|
+
print("Shell.Skeleton - Error: Could not create a bounding rectangle face. Returning None.")
|
1831
|
+
return None
|
1832
|
+
large_shell = Topology.Boolean(bf, roof, operation="slice", tolerance=tolerance)
|
1833
|
+
if not large_shell:
|
1834
|
+
return None
|
1835
|
+
faces = Topology.Faces(large_shell)
|
1836
|
+
if not faces:
|
1837
|
+
return None
|
1838
|
+
final_faces = []
|
1839
|
+
for f in faces:
|
1840
|
+
internalBoundaries = Face.InternalBoundaries(f)
|
1841
|
+
if len(internalBoundaries) == 0:
|
1842
|
+
final_faces.append(f)
|
1843
|
+
shell = Shell.ByFaces(final_faces, tolerance=tolerance)
|
1844
|
+
if not Topology.IsInstance(shell, "Shell"):
|
1845
|
+
print("Shell.Skeleton - Error: Could not create shell. Returning None.")
|
1846
|
+
return None
|
1847
|
+
return shell
|
1848
|
+
|
1849
1849
|
@staticmethod
|
1850
1850
|
def Square(origin= None, size: float = 1.0,
|
1851
1851
|
uSides: int = 2, vSides: int = 2, direction: list = [0, 0, 1],
|
@@ -1882,6 +1882,33 @@ class Shell():
|
|
1882
1882
|
uSides=uSides, vSides=vSides, direction=direction,
|
1883
1883
|
placement=placement, tolerance=tolerance)
|
1884
1884
|
|
1885
|
+
@staticmethod
|
1886
|
+
def _grow_connected_group(seed_idx, group_size, adjacency, visited_global):
|
1887
|
+
"""
|
1888
|
+
Attempts to grow a group of the given size starting from seed_idx using adjacency.
|
1889
|
+
Returns a list of indices if successful, else None.
|
1890
|
+
"""
|
1891
|
+
from collections import deque
|
1892
|
+
import random
|
1893
|
+
|
1894
|
+
group = [seed_idx]
|
1895
|
+
visited = set(group)
|
1896
|
+
queue = deque([seed_idx])
|
1897
|
+
|
1898
|
+
while queue and len(group) < group_size:
|
1899
|
+
current = queue.popleft()
|
1900
|
+
neighbors = adjacency.get(current, [])
|
1901
|
+
random.shuffle(neighbors)
|
1902
|
+
for neighbor in neighbors:
|
1903
|
+
if neighbor not in visited and neighbor not in visited_global:
|
1904
|
+
group.append(neighbor)
|
1905
|
+
visited.add(neighbor)
|
1906
|
+
queue.append(neighbor)
|
1907
|
+
if len(group) >= group_size:
|
1908
|
+
break
|
1909
|
+
|
1910
|
+
return group if len(group) == group_size else None
|
1911
|
+
|
1885
1912
|
@staticmethod
|
1886
1913
|
def Vertices(shell) -> list:
|
1887
1914
|
"""
|
topologicpy/Topology.py
CHANGED
@@ -7897,13 +7897,6 @@ class Topology():
|
|
7897
7897
|
calframe = inspect.getouterframes(curframe, 2)
|
7898
7898
|
print('caller name:', calframe[1][3])
|
7899
7899
|
return topology
|
7900
|
-
# if len(dictionary.Keys()) < 1:
|
7901
|
-
# if not silent:
|
7902
|
-
# print("Topology.SetDictionary - Warning: the input dictionary parameter is empty. Returning original input.")
|
7903
|
-
# curframe = inspect.currentframe()
|
7904
|
-
# calframe = inspect.getouterframes(curframe, 2)
|
7905
|
-
# print('caller name:', calframe[1][3])
|
7906
|
-
# return topology
|
7907
7900
|
_ = topology.SetDictionary(dictionary)
|
7908
7901
|
return topology
|
7909
7902
|
|
@@ -8861,6 +8854,177 @@ class Topology():
|
|
8861
8854
|
pass
|
8862
8855
|
return returnTopology
|
8863
8856
|
|
8857
|
+
@staticmethod
|
8858
|
+
def SubCombinations(topology,
|
8859
|
+
subTopologyType=None,
|
8860
|
+
minSize: int = 2,
|
8861
|
+
maxSize: int = None,
|
8862
|
+
maxCombinations: int = 100,
|
8863
|
+
timeLimit: int = 10,
|
8864
|
+
removeCoplanarFaces: bool = False,
|
8865
|
+
removeCollinearEdges: bool = False,
|
8866
|
+
tolerance: float = 0.001,
|
8867
|
+
silent: bool = False):
|
8868
|
+
"""
|
8869
|
+
Creates connected sub-combination topologies of the input topology. Warning: This is prone to combinatorial explosion.
|
8870
|
+
|
8871
|
+
Parameters
|
8872
|
+
----------
|
8873
|
+
topology : topologic_core.Topology
|
8874
|
+
The input topology. This cannot be vertex or edge.
|
8875
|
+
subTopologyType : str , optional except when topology is a Cluster
|
8876
|
+
The type of subTopology to include in the combinations.
|
8877
|
+
If the input is a Cluster, you must specify the subTopologyType.
|
8878
|
+
The options are (case insensitive):
|
8879
|
+
- "CellComplex"
|
8880
|
+
- "Shell"
|
8881
|
+
- "Wire"
|
8882
|
+
If set to None, it will be set according to the input topology type as follows:
|
8883
|
+
- "Cluster" -> User-defined
|
8884
|
+
- "CellComplex" -> "CellComplexes"
|
8885
|
+
- "Cell" -> "Shells"
|
8886
|
+
- "Shell" -> "Shells"
|
8887
|
+
- "Face" -> "Wires"
|
8888
|
+
- "Wire" -> "Wires"
|
8889
|
+
- "Edge" -> None
|
8890
|
+
- "Vertex" -> None
|
8891
|
+
- "Graph" -> "Graphs"
|
8892
|
+
minSize : int , optional
|
8893
|
+
The minimum number of subtopologies to include in a combination. This number cannot be less than 2. The default is 2.
|
8894
|
+
maxSize : int , optional
|
8895
|
+
The maximum number of faces to include in a combinations. This number cannot be less than the number of subtopologies minus 1. The default is None which means the maximum will be set to the number of subtopologies minus 1.
|
8896
|
+
maxCombinations : int , optional
|
8897
|
+
The maximum number of combinations to create. The default is 100.
|
8898
|
+
timeLimit : int , optional
|
8899
|
+
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 Shells.
|
8900
|
+
removeCoplanarFaces : bool , optional
|
8901
|
+
If set to True, coplanar faces are removed. Otherwise they are not. The default is False.
|
8902
|
+
removeCollinearEdges : bool , optional
|
8903
|
+
If set to True, collinear edges are removed. Otherwise they are not. The default is False.
|
8904
|
+
tolerance : float , optional
|
8905
|
+
The desired tolerance. The default is 0.0001.
|
8906
|
+
silent : bool , optional
|
8907
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
8908
|
+
|
8909
|
+
Returns
|
8910
|
+
-------
|
8911
|
+
list
|
8912
|
+
The list of created sub-combinations.
|
8913
|
+
|
8914
|
+
"""
|
8915
|
+
from topologicpy.Cluster import Cluster
|
8916
|
+
from topologicpy.Graph import Graph
|
8917
|
+
from topologicpy.Topology import Topology
|
8918
|
+
from topologicpy.Dictionary import Dictionary
|
8919
|
+
from topologicpy.Helper import Helper
|
8920
|
+
import random
|
8921
|
+
import time
|
8922
|
+
|
8923
|
+
if Topology.IsInstance(topology, "cluster") and subTopologyType is None:
|
8924
|
+
if not silent:
|
8925
|
+
print("Topology.SubCombinations - Error: The topology input parameter is a cluster but the subTopologyType is not specified. Returning None.")
|
8926
|
+
return None
|
8927
|
+
|
8928
|
+
if Topology.IsInstance(topology, "vertex") or Topology.IsInstance(topology, "edge"):
|
8929
|
+
if not silent:
|
8930
|
+
print("Topology.SubCombinations - Error: The topology input parameter cannot be a vertex or an edge. Returning None.")
|
8931
|
+
return None
|
8932
|
+
|
8933
|
+
mapping = {
|
8934
|
+
"cellcomplex": "cell",
|
8935
|
+
"cell": "face",
|
8936
|
+
"shell": "face",
|
8937
|
+
"face": "edge",
|
8938
|
+
"wire": "edge",
|
8939
|
+
"graph": "vertex"
|
8940
|
+
}
|
8941
|
+
|
8942
|
+
type_string = Topology.TypeAsString(topology).lower()
|
8943
|
+
if type_string not in mapping:
|
8944
|
+
if not silent:
|
8945
|
+
print(f"Topology.SubCombinations - Error: The input topology type ({type_string}) is not supported. Returning None.")
|
8946
|
+
return None
|
8947
|
+
|
8948
|
+
if subTopologyType is None:
|
8949
|
+
subTopologyType = mapping[type_string]
|
8950
|
+
subTopologyType = subTopologyType.lower()
|
8951
|
+
|
8952
|
+
if subTopologyType not in ["cell", "face", "edge", "vertex"]:
|
8953
|
+
if not silent:
|
8954
|
+
print(f"Topology.SubCombinations - Error: Unknown subTopologyType: {subTopologyType}. Returning None.")
|
8955
|
+
return None
|
8956
|
+
|
8957
|
+
if ((type_string in ["cell", "shell"]) and subTopologyType not in ["face", "edge"]) or \
|
8958
|
+
((type_string in ["wire", "face"]) and subTopologyType != "edge"):
|
8959
|
+
if not silent:
|
8960
|
+
print(f"Topology.SubCombinations - Error: The subTopology type {subTopologyType} is not appropriate for topology of type {type_string}. Returning None.")
|
8961
|
+
return None
|
8962
|
+
|
8963
|
+
all_subtopologies = Topology.SubTopologies(topology, subTopologyType=subTopologyType, silent=silent)
|
8964
|
+
num_subtopologies = len(all_subtopologies)
|
8965
|
+
if num_subtopologies < 2:
|
8966
|
+
if not silent:
|
8967
|
+
print("Topology.SubCombinations - Warning: Not enough subtopologies found. Returning empty list.")
|
8968
|
+
return []
|
8969
|
+
|
8970
|
+
indices = list(range(num_subtopologies))
|
8971
|
+
minSize = max(2, minSize)
|
8972
|
+
maxSize = num_subtopologies - 1 if maxSize is None else min(maxSize, num_subtopologies - 1)
|
8973
|
+
|
8974
|
+
subt_label_key = "__index__"
|
8975
|
+
all_subtopologies = [Topology.SetDictionary(subt, Dictionary.ByKeyValue(subt_label_key, i)) for i, subt in enumerate(all_subtopologies)]
|
8976
|
+
|
8977
|
+
group_sizes = list(range(minSize, maxSize + 1))
|
8978
|
+
num_sizes = len(group_sizes)
|
8979
|
+
per_size_combinations = maxCombinations // num_sizes
|
8980
|
+
per_size_time = timeLimit / num_sizes
|
8981
|
+
|
8982
|
+
adjacency = Dictionary.AdjacencyDictionary(topology, subTopologyType=subTopologyType, labelKey=subt_label_key, weightKey=None, includeWeights=False)
|
8983
|
+
|
8984
|
+
results = []
|
8985
|
+
seen_groups = set()
|
8986
|
+
|
8987
|
+
for group_size in reversed(group_sizes):
|
8988
|
+
size_start_time = time.time()
|
8989
|
+
remaining = per_size_combinations
|
8990
|
+
tries = 0
|
8991
|
+
max_tries = per_size_combinations * 10
|
8992
|
+
|
8993
|
+
while remaining > 0 and (time.time() - size_start_time) < per_size_time and tries < max_tries:
|
8994
|
+
random.shuffle(indices)
|
8995
|
+
for seed in indices:
|
8996
|
+
if (time.time() - size_start_time) > per_size_time:
|
8997
|
+
break
|
8998
|
+
group = Helper.Grow(seed, group_size, adjacency, visited_global=seen_groups)
|
8999
|
+
if group:
|
9000
|
+
key = tuple(sorted(group))
|
9001
|
+
if key not in seen_groups:
|
9002
|
+
tries += 1
|
9003
|
+
seen_groups.add(key)
|
9004
|
+
results.append(group)
|
9005
|
+
remaining -= 1
|
9006
|
+
if remaining <= 0 or tries >= max_tries:
|
9007
|
+
break
|
9008
|
+
|
9009
|
+
return_combinations = []
|
9010
|
+
for result in reversed(results):
|
9011
|
+
subtopologies = [all_subtopologies[i] for i in result]
|
9012
|
+
# Special case for graphs
|
9013
|
+
if Topology.IsInstance(topology, "Graph"):
|
9014
|
+
edges = Graph.Edges(topology, vertices = subtopologies)
|
9015
|
+
combination = Graph.ByVerticesEdges(subtopologies, edges)
|
9016
|
+
else:
|
9017
|
+
combination = Topology.SelfMerge(Cluster.ByTopologies(subtopologies), tolerance=tolerance)
|
9018
|
+
if removeCollinearEdges:
|
9019
|
+
if Topology.IsInstance(combination, "face") or Topology.IsInstance(combination, "wire"):
|
9020
|
+
combination = Topology.RemoveCollinearEdges(combination, tolerance=tolerance)
|
9021
|
+
if removeCoplanarFaces:
|
9022
|
+
if Topology.IsInstance(combination, "cellcomplex") or Topology.IsInstance(combination, "cell") or Topology.IsInstance(combination, "shell"):
|
9023
|
+
combination = Topology.RemoveCoplanarFaces(combination, tolerance=tolerance)
|
9024
|
+
return_combinations.append(combination)
|
9025
|
+
|
9026
|
+
return return_combinations
|
9027
|
+
|
8864
9028
|
@staticmethod
|
8865
9029
|
def Taper(topology, origin=None, ratioRange: list = [0, 1], triangulate: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
|
8866
9030
|
"""
|
@@ -9188,7 +9352,7 @@ class Topology():
|
|
9188
9352
|
return Topology.SubTopologies(topology=topology, subTopologyType="cluster")
|
9189
9353
|
|
9190
9354
|
@staticmethod
|
9191
|
-
def SubTopologies(topology, subTopologyType="vertex"):
|
9355
|
+
def SubTopologies(topology, subTopologyType="vertex", silent: bool = False):
|
9192
9356
|
"""
|
9193
9357
|
Returns the subtopologies of the input topology as specified by the subTopologyType input string.
|
9194
9358
|
|
@@ -9198,7 +9362,8 @@ class Topology():
|
|
9198
9362
|
The input topology.
|
9199
9363
|
subTopologyType : str , optional
|
9200
9364
|
The requested subtopology type. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "vertex".
|
9201
|
-
|
9365
|
+
silent : bool , optional
|
9366
|
+
If set to True, no warnings or errors will be printed. The default is False.
|
9202
9367
|
Returns
|
9203
9368
|
-------
|
9204
9369
|
list
|
@@ -9206,15 +9371,28 @@ class Topology():
|
|
9206
9371
|
|
9207
9372
|
"""
|
9208
9373
|
from topologicpy.Face import Face
|
9374
|
+
from topologicpy.Graph import Graph
|
9209
9375
|
|
9210
|
-
if not Topology.IsInstance(topology, "Topology"):
|
9211
|
-
|
9376
|
+
if not Topology.IsInstance(topology, "Topology") and not Topology.IsInstance(topology, "Graph"):
|
9377
|
+
if not silent:
|
9378
|
+
print("Topology.SubTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
|
9212
9379
|
return None
|
9213
9380
|
if Topology.TypeAsString(topology).lower() == subTopologyType.lower():
|
9214
9381
|
return [topology]
|
9215
9382
|
|
9216
9383
|
subTopologies = []
|
9217
9384
|
|
9385
|
+
# Special case for Graphs
|
9386
|
+
if Topology.IsInstance(topology, "graph"):
|
9387
|
+
if subTopologyType.lower() == "vertex":
|
9388
|
+
return Graph.Vertices(topology)
|
9389
|
+
elif subTopologyType.lower() == "edge":
|
9390
|
+
return Graph.Edges(topology)
|
9391
|
+
else:
|
9392
|
+
if not silent:
|
9393
|
+
print(f"Topology.SubTopologies - Error: the input subTopologyType parameter {subTopologyType} is not a valid subTopology of Graphs. Returning None.")
|
9394
|
+
return None
|
9395
|
+
|
9218
9396
|
# Spcecial case for faces to return vertices in CW/CCW order.
|
9219
9397
|
if Topology.IsInstance(topology, "face") and (subTopologyType.lower() == "vertex" or subTopologyType.lower() == "edge"):
|
9220
9398
|
wires = Face.Wires(topology)
|
topologicpy/Wire.py
CHANGED
@@ -4629,7 +4629,7 @@ class Wire():
|
|
4629
4629
|
"""
|
4630
4630
|
sv, ev = Wire.StartEndVertices(wire)
|
4631
4631
|
return sv
|
4632
|
-
|
4632
|
+
|
4633
4633
|
@staticmethod
|
4634
4634
|
def Trapezoid(origin= 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):
|
4635
4635
|
"""
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.8.
|
1
|
+
__version__ = '0.8.35'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.35
|
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
|
@@ -3,18 +3,18 @@ topologicpy/Aperture.py,sha256=wNn5miB_IrGCBYuQ18HXQYRva20dUC3id4AJCulL7to,2723
|
|
3
3
|
topologicpy/BVH.py,sha256=ti-23A2HIxaqnJ3C9GWhCjQev9qQwdrSfZVfXVZujYE,13127
|
4
4
|
topologicpy/CSG.py,sha256=hqFPg3RvAnRgzwyyWbc4N80ZYO9AfvbWn0RsjXvaz8k,15695
|
5
5
|
topologicpy/Cell.py,sha256=WQyi2xKT5PFovGZzu1oiw0ICuJultsOXnNbfELrMLTI,173467
|
6
|
-
topologicpy/CellComplex.py,sha256=
|
6
|
+
topologicpy/CellComplex.py,sha256=muhqLpnlZXZKVSF3j_v4uF1LbdVyrZqmLqH_uoACYRU,60919
|
7
7
|
topologicpy/Cluster.py,sha256=Wv81yPlQ3Qlnylpvs2aBVNV77M0Z9oHTUfTeLbAuX74,58790
|
8
8
|
topologicpy/Color.py,sha256=ZVVQRKGjebY9aOU1gpN_AbssdRRiVKlZV3f8TrsTNgg,20307
|
9
9
|
topologicpy/Context.py,sha256=G3CwMvN8Jw2rnQRwB-n4MaQq_wLS0vPimbXKwsdMJ80,3055
|
10
10
|
topologicpy/DGL.py,sha256=HQXy9iDnrvWGDxaBfe5pRbweQ2zLBvAf6UdjfhKkQYI,139041
|
11
|
-
topologicpy/Dictionary.py,sha256=
|
11
|
+
topologicpy/Dictionary.py,sha256=Rdd8RwBkuoqndcDWOHfO5oZHngoF2brNdWEB7P5Bns4,40900
|
12
12
|
topologicpy/Edge.py,sha256=6M7UMPZj_JXXH9mFieEcQu3haYQ6Rn64yRcL0b_axl8,73484
|
13
13
|
topologicpy/EnergyModel.py,sha256=Pyb28gDDwhzlQIH0xqAygqS0P3SJxWyyV7OWS_AAfRs,53856
|
14
14
|
topologicpy/Face.py,sha256=1r6j4DOhDJEVyRTuFhxh1wmRMEvqZe8FQ0_Yy-J0pzg,202021
|
15
|
-
topologicpy/Graph.py,sha256=
|
15
|
+
topologicpy/Graph.py,sha256=VNJsJpJW2fJADzgT_yYDljEc_2YjGgHBmWjIqcVplNE,580758
|
16
16
|
topologicpy/Grid.py,sha256=EbI2NcYhQDpD5mItd7A1Lpr8Puuf87vZPWuoh7_gChQ,18483
|
17
|
-
topologicpy/Helper.py,sha256=
|
17
|
+
topologicpy/Helper.py,sha256=9YdcJ8P6HYi-3v0lV_QNQw-PwUcl3TnBrw2QPBdNiqo,31287
|
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
|
@@ -22,17 +22,17 @@ topologicpy/Plotly.py,sha256=5E6M2N0MbdkA5iMZ_Y71PaPB9wilEneYVUba8RqeKe0,119400
|
|
22
22
|
topologicpy/Polyskel.py,sha256=oVfM4lqSMPTjnkHfsRU9VI8Blt6Vf0LVPkD9ebz7Wmw,27082
|
23
23
|
topologicpy/PyG.py,sha256=zvV6jtnol_aFiN6JRoMpYwBVfOU2aFs9gdWSdEo6mtU,109757
|
24
24
|
topologicpy/ShapeGrammar.py,sha256=ay2kOkMvoa_KqGd_ZCLgDk0hmyMniI636N_YbMsyM60,23259
|
25
|
-
topologicpy/Shell.py,sha256=
|
25
|
+
topologicpy/Shell.py,sha256=4zqC5m7RRQAmeIugqH098vSfBuW77L9ao5erdh8gA1Y,90585
|
26
26
|
topologicpy/Speckle.py,sha256=-eiTqJugd7pHiHpD3pDUcDO6CGhVyPV14HFRzaqEoaw,18187
|
27
27
|
topologicpy/Sun.py,sha256=_VBBAUIDhvpkp72JBZlv7k9qx9jYubm3yM56UZ1Nc6c,36837
|
28
|
-
topologicpy/Topology.py,sha256=
|
28
|
+
topologicpy/Topology.py,sha256=Bc16DUPrigdTiN_ShS6JViHRjsmR3jxChRBTe12S7Cw,466405
|
29
29
|
topologicpy/Vector.py,sha256=mx7fgABdioikPWM9HzXKzmqfx3u_XBcU_jlLD4qK2x8,42407
|
30
30
|
topologicpy/Vertex.py,sha256=sY9M7sMep7iTGiRYlmr8ed18Yz_RLyH6-Fm_L6SJTTw,84688
|
31
|
-
topologicpy/Wire.py,sha256=
|
31
|
+
topologicpy/Wire.py,sha256=lxlooqXjSWntwlg_h6AepNpOw_JPDIUpQKkrvvNYERk,231719
|
32
32
|
topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
|
33
|
-
topologicpy/version.py,sha256=
|
34
|
-
topologicpy-0.8.
|
35
|
-
topologicpy-0.8.
|
36
|
-
topologicpy-0.8.
|
37
|
-
topologicpy-0.8.
|
38
|
-
topologicpy-0.8.
|
33
|
+
topologicpy/version.py,sha256=3ZOp-2mi5vS_FqQuNhjU7Os4xS6SwCO3uIAj_kgDx28,23
|
34
|
+
topologicpy-0.8.35.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
35
|
+
topologicpy-0.8.35.dist-info/METADATA,sha256=vRbd2Mu3HprTBjKk-chx8zZXvRdPIm7x0I_oPvNhdaA,10535
|
36
|
+
topologicpy-0.8.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
37
|
+
topologicpy-0.8.35.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
38
|
+
topologicpy-0.8.35.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|