topologicpy 0.7.71__py3-none-any.whl → 0.7.72__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/BVH.py +9 -7
- topologicpy/CellComplex.py +34 -23
- topologicpy/Cluster.py +1 -1
- topologicpy/Color.py +67 -1
- topologicpy/Dictionary.py +108 -10
- topologicpy/Edge.py +8 -4
- topologicpy/Face.py +1 -1
- topologicpy/Graph.py +18 -6
- topologicpy/Helper.py +111 -0
- topologicpy/Plotly.py +507 -344
- topologicpy/Shell.py +24 -24
- topologicpy/Topology.py +234 -117
- topologicpy/Wire.py +50 -56
- topologicpy/version.py +1 -1
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.72.dist-info}/METADATA +1 -1
- topologicpy-0.7.72.dist-info/RECORD +36 -0
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.72.dist-info}/WHEEL +1 -1
- topologicpy-0.7.71.dist-info/RECORD +0 -36
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.72.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.72.dist-info}/top_level.txt +0 -0
topologicpy/BVH.py
CHANGED
@@ -41,6 +41,8 @@ class BVH:
|
|
41
41
|
|
42
42
|
def intersects(self, other):
|
43
43
|
# Check if this AABB intersects with another AABB
|
44
|
+
if other == None:
|
45
|
+
return False
|
44
46
|
return np.all(self.min_point <= other.max_point) and np.all(self.max_point >= other.min_point)
|
45
47
|
|
46
48
|
def contains(self, point):
|
@@ -200,13 +202,13 @@ class BVH:
|
|
200
202
|
cluster = Cluster.ByTopologies(vertices)
|
201
203
|
bb = Topology.BoundingBox(cluster)
|
202
204
|
d = Topology.Dictionary(bb)
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
query_aabb = BVH.AABB(min_point=(
|
205
|
+
x_min = Dictionary.ValueAtKey(d, "xmin")
|
206
|
+
y_min = Dictionary.ValueAtKey(d, "ymin")
|
207
|
+
z_min = Dictionary.ValueAtKey(d, "zmin")
|
208
|
+
x_max = Dictionary.ValueAtKey(d, "zmax")
|
209
|
+
y_max = Dictionary.ValueAtKey(d, "ymax")
|
210
|
+
z_max = Dictionary.ValueAtKey(d, "zmax")
|
211
|
+
query_aabb = BVH.AABB(min_point=(x_min, y_min, z_min), max_point=(x_max, y_max, z_max))
|
210
212
|
return query_aabb
|
211
213
|
|
212
214
|
def Clashes(bvh, query):
|
topologicpy/CellComplex.py
CHANGED
@@ -103,6 +103,8 @@ class CellComplex():
|
|
103
103
|
If set to True, any dictionaries in the cells are transferred to the CellComplex. Otherwise, they are not. The default is False.
|
104
104
|
tolerance : float , optional
|
105
105
|
The desired tolerance. The default is 0.0001.
|
106
|
+
silent : bool , optional
|
107
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
106
108
|
|
107
109
|
Returns
|
108
110
|
-------
|
@@ -192,7 +194,7 @@ class CellComplex():
|
|
192
194
|
return CellComplex.ByCells(cells, tolerance)
|
193
195
|
|
194
196
|
@staticmethod
|
195
|
-
def ByFaces(faces: list, tolerance: float = 0.0001):
|
197
|
+
def ByFaces(faces: list, tolerance: float = 0.0001, silent: bool = False):
|
196
198
|
"""
|
197
199
|
Creates a cellcomplex by merging the input faces.
|
198
200
|
|
@@ -202,6 +204,8 @@ class CellComplex():
|
|
202
204
|
The input faces.
|
203
205
|
tolerance : float , optional
|
204
206
|
The desired tolerance. The default is 0.0001.
|
207
|
+
silent : bool , optional
|
208
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
205
209
|
|
206
210
|
Returns
|
207
211
|
-------
|
@@ -213,38 +217,45 @@ class CellComplex():
|
|
213
217
|
from topologicpy.Topology import Topology
|
214
218
|
|
215
219
|
if not isinstance(faces, list):
|
216
|
-
|
220
|
+
if not silent:
|
221
|
+
print("CellComplex.ByFaces - Error: The input faces parameter is not a valid list. Returning None.")
|
217
222
|
return None
|
218
223
|
faces = [x for x in faces if Topology.IsInstance(x, "Face")]
|
219
224
|
if len(faces) < 1:
|
220
|
-
|
225
|
+
if not silent:
|
226
|
+
print("CellComplex.ByFaces - Error: The input faces parameter does not contain any valid faces. Returning None.")
|
221
227
|
return None
|
222
228
|
try:
|
223
229
|
cellComplex = topologic.CellComplex.ByFaces(faces, tolerance, False) # Hook to Core
|
224
230
|
except:
|
225
231
|
cellComplex = None
|
226
232
|
if not cellComplex:
|
227
|
-
|
233
|
+
if not silent:
|
234
|
+
print("CellComplex.ByFaces - Warning: The default method failed. Attempting a workaround.")
|
228
235
|
cellComplex = faces[0]
|
229
236
|
for i in range(1,len(faces)):
|
230
237
|
newCellComplex = None
|
231
238
|
try:
|
232
239
|
newCellComplex = cellComplex.Merge(faces[i], False, tolerance)
|
233
240
|
except:
|
234
|
-
|
241
|
+
if not silent:
|
242
|
+
print("CellComplex.ByFaces - Warning: Failed to merge face #"+str(i)+". Skipping.")
|
235
243
|
if newCellComplex:
|
236
244
|
cellComplex = newCellComplex
|
237
245
|
if not Topology.Type(cellComplex) == Topology.TypeID("CellComplex"):
|
238
|
-
|
246
|
+
if not silent:
|
247
|
+
print("CellComplex.ByFaces - Warning: The input faces do not form a cellcomplex")
|
239
248
|
if Topology.Type(cellComplex) == Topology.TypeID("Cluster"):
|
240
249
|
returnCellComplexes = Cluster.CellComplexes(cellComplex)
|
241
250
|
if len(returnCellComplexes) > 0:
|
242
251
|
return returnCellComplexes[0]
|
243
252
|
else:
|
244
|
-
|
253
|
+
if not silent:
|
254
|
+
print("CellComplex.ByFaces - Error: Could not create a cellcomplex. Returning None.")
|
245
255
|
return None
|
246
256
|
else:
|
247
|
-
|
257
|
+
if not silent:
|
258
|
+
print("CellComplex.ByFaces - Error: Could not create a cellcomplex. Returning None.")
|
248
259
|
return None
|
249
260
|
else:
|
250
261
|
return cellComplex
|
@@ -892,33 +903,33 @@ class CellComplex():
|
|
892
903
|
x.append(Vertex.X(aVertex, mantissa=mantissa))
|
893
904
|
y.append(Vertex.Y(aVertex, mantissa=mantissa))
|
894
905
|
z.append(Vertex.Z(aVertex, mantissa=mantissa))
|
895
|
-
|
896
|
-
|
897
|
-
|
906
|
+
x_min = min(x)
|
907
|
+
y_min = min(y)
|
908
|
+
z_min = min(z)
|
898
909
|
maxX = max(x)
|
899
910
|
maxY = max(y)
|
900
911
|
maxZ = max(z)
|
901
|
-
return [
|
912
|
+
return [x_min, y_min, z_min, maxX, maxY, maxZ]
|
902
913
|
|
903
914
|
def slice(topology, uSides, vSides, wSides):
|
904
|
-
|
905
|
-
centroid = Vertex.ByCoordinates(
|
906
|
-
wOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), Vertex.Y(centroid, mantissa=mantissa),
|
907
|
-
wFace = Face.Rectangle(origin=wOrigin, width=(maxX-
|
915
|
+
x_min, y_min, z_min, maxX, maxY, maxZ = bb(topology)
|
916
|
+
centroid = Vertex.ByCoordinates(x_min+(maxX-x_min)*0.5, y_min+(maxY-y_min)*0.5, z_min+(maxZ-z_min)*0.5)
|
917
|
+
wOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), Vertex.Y(centroid, mantissa=mantissa), z_min)
|
918
|
+
wFace = Face.Rectangle(origin=wOrigin, width=(maxX-x_min)*1.1, length=(maxY-y_min)*1.1)
|
908
919
|
wFaces = []
|
909
|
-
wOffset = (maxZ-
|
920
|
+
wOffset = (maxZ-z_min)/wSides
|
910
921
|
for i in range(wSides-1):
|
911
922
|
wFaces.append(Topology.Translate(wFace, 0,0,wOffset*(i+1)))
|
912
|
-
uOrigin = Vertex.ByCoordinates(
|
913
|
-
uFace = Face.Rectangle(origin=uOrigin, width=(maxZ-
|
923
|
+
uOrigin = Vertex.ByCoordinates(x_min, Vertex.Y(centroid, mantissa=mantissa), Vertex.Z(centroid, mantissa=mantissa))
|
924
|
+
uFace = Face.Rectangle(origin=uOrigin, width=(maxZ-z_min)*1.1, length=(maxY-y_min)*1.1, direction=[1,0,0])
|
914
925
|
uFaces = []
|
915
|
-
uOffset = (maxX-
|
926
|
+
uOffset = (maxX-x_min)/uSides
|
916
927
|
for i in range(uSides-1):
|
917
928
|
uFaces.append(Topology.Translate(uFace, uOffset*(i+1),0,0))
|
918
|
-
vOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa),
|
919
|
-
vFace = Face.Rectangle(origin=vOrigin, width=(maxX-
|
929
|
+
vOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), y_min, Vertex.Z(centroid, mantissa=mantissa))
|
930
|
+
vFace = Face.Rectangle(origin=vOrigin, width=(maxX-x_min)*1.1, length=(maxZ-z_min)*1.1, direction=[0,1,0])
|
920
931
|
vFaces = []
|
921
|
-
vOffset = (maxY-
|
932
|
+
vOffset = (maxY-y_min)/vSides
|
922
933
|
for i in range(vSides-1):
|
923
934
|
vFaces.append(Topology.Translate(vFace, 0,vOffset*(i+1),0))
|
924
935
|
all_faces = uFaces+vFaces+wFaces
|
topologicpy/Cluster.py
CHANGED
@@ -166,7 +166,7 @@ class Cluster():
|
|
166
166
|
from topologicpy.Topology import Topology
|
167
167
|
from topologicpy.Helper import Helper
|
168
168
|
import inspect
|
169
|
-
|
169
|
+
|
170
170
|
if len(args) == 0:
|
171
171
|
if not silent:
|
172
172
|
print("Cluster.ByTopologies - Error: The input topologies parameter is an empty list. Returning None.")
|
topologicpy/Color.py
CHANGED
@@ -18,6 +18,44 @@ import plotly.colors
|
|
18
18
|
import math
|
19
19
|
|
20
20
|
class Color:
|
21
|
+
|
22
|
+
@staticmethod
|
23
|
+
def AnyToHex(color):
|
24
|
+
"""
|
25
|
+
Converts a color to a hexadecimal color string.
|
26
|
+
|
27
|
+
Parameters
|
28
|
+
----------
|
29
|
+
color : list or str
|
30
|
+
The input color parameter which can be any of RGB, CMYK, CSS Named Color, or Hex
|
31
|
+
|
32
|
+
Returns
|
33
|
+
-------
|
34
|
+
str
|
35
|
+
A hexadecimal color string in the format '#RRGGBB'.
|
36
|
+
"""
|
37
|
+
return_hex = None
|
38
|
+
if isinstance(color, list):
|
39
|
+
if len(color) == 4: # Probably CMYK
|
40
|
+
if all(0 <= x <= 1 for x in color[:4]):
|
41
|
+
return_hex = Color.CMYKToHex(color[:4])
|
42
|
+
elif len(color) == 3:
|
43
|
+
if all(0 <= x <= 255 for x in color[:3]):
|
44
|
+
return_hex = Color.RGBToHex(color[:3])
|
45
|
+
elif isinstance(color, str): # Probably a CSSColor
|
46
|
+
if color.lower() in [x.lower() for x in Color.CSSNamedColors()]:
|
47
|
+
rgb = Color.ByCSSNamedColor(color.lower())
|
48
|
+
return_hex = Color.RGBToHex(rgb)
|
49
|
+
else: # Probably alread a HEX or other Plotly-compatible string
|
50
|
+
return_hex = color
|
51
|
+
|
52
|
+
if not isinstance(return_hex, str):
|
53
|
+
print("Color.AnyToHex - Error: Could not recognize the input parameter. Returning None.")
|
54
|
+
return None
|
55
|
+
|
56
|
+
return return_hex.upper()
|
57
|
+
|
58
|
+
|
21
59
|
@staticmethod
|
22
60
|
def ByCSSNamedColor(color, alpha: float = None):
|
23
61
|
"""
|
@@ -262,6 +300,34 @@ class Color:
|
|
262
300
|
rgbList.append(alpha)
|
263
301
|
return rgbList
|
264
302
|
|
303
|
+
@staticmethod
|
304
|
+
def CMYKToHex(cmyk):
|
305
|
+
"""
|
306
|
+
Convert a CMYK color (list of 4 values) to its hexadecimal representation.
|
307
|
+
|
308
|
+
Parameters
|
309
|
+
----------
|
310
|
+
color : list
|
311
|
+
cmyk (list or tuple): CMYK color values as [C, M, Y, K], each in the range 0 to 1.
|
312
|
+
|
313
|
+
Returns
|
314
|
+
-------
|
315
|
+
str: The hexadecimal color string for Plotly (e.g., '#FFFFFF').
|
316
|
+
"""
|
317
|
+
c, m, y, k = cmyk
|
318
|
+
|
319
|
+
# Convert CMYK to RGB
|
320
|
+
r = 255 * (1 - c) * (1 - k)
|
321
|
+
g = 255 * (1 - m) * (1 - k)
|
322
|
+
b = 255 * (1 - y) * (1 - k)
|
323
|
+
|
324
|
+
# Clamp RGB values to 0-255 range and convert to integers
|
325
|
+
r, g, b = int(round(r)), int(round(g)), int(round(b))
|
326
|
+
|
327
|
+
# Convert RGB to hex format
|
328
|
+
hex_color = "#{:02x}{:02x}{:02x}".format(r, g, b)
|
329
|
+
return hex_color
|
330
|
+
|
265
331
|
@staticmethod
|
266
332
|
def CSSNamedColor(color):
|
267
333
|
"""
|
@@ -355,7 +421,7 @@ class Color:
|
|
355
421
|
"mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
356
422
|
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace",
|
357
423
|
"olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise",
|
358
|
-
"palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple",
|
424
|
+
"palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple",
|
359
425
|
"red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna",
|
360
426
|
"silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
|
361
427
|
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"
|
topologicpy/Dictionary.py
CHANGED
@@ -344,6 +344,97 @@ class Dictionary():
|
|
344
344
|
values.append(pythonDictionary[key])
|
345
345
|
return Dictionary.ByKeysValues(keys, values)
|
346
346
|
|
347
|
+
@staticmethod
|
348
|
+
def Filter(elements, dictionaries, searchType="any", key=None, value=None):
|
349
|
+
"""
|
350
|
+
Filters the input list of dictionaries based on the input parameters.
|
351
|
+
|
352
|
+
Parameters
|
353
|
+
----------
|
354
|
+
elements : list
|
355
|
+
The input list of elements to be filtered according to the input dictionaries.
|
356
|
+
dictionaries : list
|
357
|
+
The input list of dictionaries to be filtered.
|
358
|
+
searchType : str , optional
|
359
|
+
The type of search query to conduct in the topology's dictionary. This can be one of "any", "equal to", "contains", "starts with", "ends with", "not equal to", "does not contain". The default is "any".
|
360
|
+
key : str , optional
|
361
|
+
The dictionary key to search within. The default is None which means it will filter by topology type only.
|
362
|
+
value : str , optional
|
363
|
+
The value to search for at the specified key. The default is None which means it will filter by topology type only.
|
364
|
+
|
365
|
+
Returns
|
366
|
+
-------
|
367
|
+
dict
|
368
|
+
A dictionary of filtered and other elements. The dictionary has two keys:
|
369
|
+
- "filtered" The filtered dictionaries.
|
370
|
+
- "other" The other dictionaries that did not meet the filter criteria.
|
371
|
+
- "filteredIndices" The filtered indices of dictionaries.
|
372
|
+
- "otherIndices" The other indices of dictionaries that did not meet the filter criteria.
|
373
|
+
|
374
|
+
"""
|
375
|
+
|
376
|
+
from topologicpy.Topology import Topology
|
377
|
+
|
378
|
+
def listToString(item):
|
379
|
+
returnString = ""
|
380
|
+
if isinstance(item, list):
|
381
|
+
if len(item) < 2:
|
382
|
+
return str(item[0])
|
383
|
+
else:
|
384
|
+
returnString = item[0]
|
385
|
+
for i in range(1, len(item)):
|
386
|
+
returnString = returnString+str(item[i])
|
387
|
+
return returnString
|
388
|
+
|
389
|
+
filteredDictionaries = []
|
390
|
+
otherDictionaries = []
|
391
|
+
filteredElements = []
|
392
|
+
otherElements = []
|
393
|
+
filteredIndices = []
|
394
|
+
otherIndices = []
|
395
|
+
for i, aDictionary in enumerate(dictionaries):
|
396
|
+
if not Topology.IsInstance(aDictionary, "Dictionary"):
|
397
|
+
continue
|
398
|
+
if value == "" or key == "" or value == None or key == None:
|
399
|
+
filteredDictionaries.append(aDictionary)
|
400
|
+
filteredIndices.append(i)
|
401
|
+
else:
|
402
|
+
if isinstance(value, list):
|
403
|
+
value.sort()
|
404
|
+
value = str(value)
|
405
|
+
value.replace("*",".+")
|
406
|
+
value = value.lower()
|
407
|
+
v = Dictionary.ValueAtKey(aDictionary, key)
|
408
|
+
if v == None:
|
409
|
+
otherDictionaries.append(aDictionary)
|
410
|
+
otherIndices.append(i)
|
411
|
+
otherElements.append(elements[i])
|
412
|
+
else:
|
413
|
+
v = str(v).lower()
|
414
|
+
if searchType.lower() == "equal to":
|
415
|
+
searchResult = (value == v)
|
416
|
+
elif searchType.lower() == "contains":
|
417
|
+
searchResult = (value in v)
|
418
|
+
elif searchType.lower() == "starts with":
|
419
|
+
searchResult = (value == v[0: len(value)])
|
420
|
+
elif searchType.lower() == "ends with":
|
421
|
+
searchResult = (value == v[len(v)-len(value):len(v)])
|
422
|
+
elif searchType.lower() == "not equal to":
|
423
|
+
searchResult = not (value == v)
|
424
|
+
elif searchType.lower() == "does not contain":
|
425
|
+
searchResult = not (value in v)
|
426
|
+
else:
|
427
|
+
searchResult = False
|
428
|
+
if searchResult == True:
|
429
|
+
filteredDictionaries.append(aDictionary)
|
430
|
+
filteredIndices.append(i)
|
431
|
+
filteredElements.append(elements[i])
|
432
|
+
else:
|
433
|
+
otherDictionaries.append(aDictionary)
|
434
|
+
otherIndices.append(i)
|
435
|
+
otherElements.append(elements[i])
|
436
|
+
return {"filteredDictionaries": filteredDictionaries, "otherDictionaries": otherDictionaries, "filteredIndices": filteredIndices, "otherIndices": otherIndices, "filteredElements": filteredElements, "otherElements": otherElements}
|
437
|
+
|
347
438
|
@staticmethod
|
348
439
|
def Keys(dictionary):
|
349
440
|
"""
|
@@ -579,7 +670,7 @@ class Dictionary():
|
|
579
670
|
temp_value = attr.StringValue()
|
580
671
|
topologies = None
|
581
672
|
try:
|
582
|
-
topologies = Topology.ByJSONString(temp_value
|
673
|
+
topologies = Topology.ByJSONString(temp_value)
|
583
674
|
except:
|
584
675
|
topologies = None
|
585
676
|
if isinstance(topologies, list):
|
@@ -654,14 +745,21 @@ class Dictionary():
|
|
654
745
|
if not silent == True:
|
655
746
|
print("Dictionary.ValueAtKey - Error: The input key parameter is not a valid str. Returning None.")
|
656
747
|
return None
|
657
|
-
if
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
748
|
+
if Topology.IsInstance(dictionary, "Dictionary"):
|
749
|
+
dic = Dictionary.PythonDictionary(dictionary)
|
750
|
+
return dic.get(key, None)
|
751
|
+
elif isinstance(dictionary, dict):
|
752
|
+
return dictionary.get(key, None)
|
753
|
+
return None
|
754
|
+
|
755
|
+
# if isinstance(dictionary, dict):
|
756
|
+
# attr = dictionary[key]
|
757
|
+
# elif Topology.IsInstance(dictionary, "Dictionary"):
|
758
|
+
# attr = dictionary.ValueAtKey(key)
|
759
|
+
# else:
|
760
|
+
# return None
|
761
|
+
# return_value = Dictionary._ConvertAttribute(attr)
|
762
|
+
# return return_value
|
665
763
|
|
666
764
|
@staticmethod
|
667
765
|
def Values(dictionary):
|
@@ -693,7 +791,7 @@ class Dictionary():
|
|
693
791
|
for key in keys:
|
694
792
|
try:
|
695
793
|
if isinstance(dictionary, dict):
|
696
|
-
attr = dictionary
|
794
|
+
attr = dictionary.get(key, None)
|
697
795
|
elif Topology.IsInstance(dictionary, "Dictionary"):
|
698
796
|
attr = Dictionary.ValueAtKey(dictionary,key)
|
699
797
|
else:
|
topologicpy/Edge.py
CHANGED
@@ -1143,6 +1143,8 @@ class Edge():
|
|
1143
1143
|
from topologicpy.Vertex import Vertex
|
1144
1144
|
from topologicpy.Topology import Topology
|
1145
1145
|
|
1146
|
+
|
1147
|
+
|
1146
1148
|
def calculate_normal(start_vertex, end_vertex):
|
1147
1149
|
start_vertex = np.array([float(x) for x in start_vertex])
|
1148
1150
|
end_vertex = np.array([float(x) for x in end_vertex])
|
@@ -1150,8 +1152,11 @@ class Edge():
|
|
1150
1152
|
# Calculate the direction vector of the edge
|
1151
1153
|
direction_vector = end_vertex - start_vertex
|
1152
1154
|
|
1153
|
-
#
|
1154
|
-
if np.isclose(direction_vector[
|
1155
|
+
# Check if the edge is vertical (only Z component)
|
1156
|
+
if np.isclose(direction_vector[0], 0) and np.isclose(direction_vector[1], 0):
|
1157
|
+
# Choose an arbitrary perpendicular vector in the X-Y plane, e.g., [1, 0, 0]
|
1158
|
+
normal_vector = np.array([1.0, 0.0, 0.0])
|
1159
|
+
elif np.isclose(direction_vector[2], 0):
|
1155
1160
|
# The edge lies in the X-Y plane; compute a perpendicular in the X-Y plane
|
1156
1161
|
normal_vector = np.array([-direction_vector[1], direction_vector[0], 0.0])
|
1157
1162
|
else:
|
@@ -1162,7 +1167,7 @@ class Edge():
|
|
1162
1167
|
# Check if the normal vector is effectively zero before normalization
|
1163
1168
|
if np.isclose(norm(normal_vector), 0):
|
1164
1169
|
return normal_vector
|
1165
|
-
|
1170
|
+
|
1166
1171
|
# Normalize the normal vector
|
1167
1172
|
normal_vector /= norm(normal_vector)
|
1168
1173
|
return normal_vector
|
@@ -1192,7 +1197,6 @@ class Edge():
|
|
1192
1197
|
|
1193
1198
|
# Calculate the normal line
|
1194
1199
|
normal_line_start, normal_line_end = calculate_normal_line(start_vertex, end_vertex)
|
1195
|
-
|
1196
1200
|
# Create the normal edge in Topologic
|
1197
1201
|
sv = Vertex.ByCoordinates(list(normal_line_start))
|
1198
1202
|
ev = Vertex.ByCoordinates(list(normal_line_end))
|
topologicpy/Face.py
CHANGED
@@ -2566,7 +2566,7 @@ class Face():
|
|
2566
2566
|
internal_face_number = len(face_internal_boundaries)
|
2567
2567
|
for i in range(internal_face_number):
|
2568
2568
|
face_internal_boundary = face_internal_boundaries[i]
|
2569
|
-
internal_vertices =
|
2569
|
+
internal_vertices = Topology.Vertices(face_internal_boundary)
|
2570
2570
|
internal_vertex_number = len(internal_vertices)
|
2571
2571
|
for j in range(internal_vertex_number):
|
2572
2572
|
gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j], mantissa=mantissa), Vertex.Z(internal_vertices[j], mantissa=mantissa), meshSize, current_vertex_number+j+1)
|
topologicpy/Graph.py
CHANGED
@@ -3971,7 +3971,7 @@ class Graph:
|
|
3971
3971
|
d = Graph.TopologicalDistance(graph, va, vb, tolerance)
|
3972
3972
|
top_dist += d
|
3973
3973
|
if top_dist == 0:
|
3974
|
-
print("Topological Distance is Zero
|
3974
|
+
print("Graph.ClosenessCentrality - Warning: Topological Distance is Zero.")
|
3975
3975
|
scores.append(0)
|
3976
3976
|
else:
|
3977
3977
|
scores.append(round((n-1)/top_dist, mantissa))
|
@@ -7847,20 +7847,24 @@ class Graph:
|
|
7847
7847
|
sides = 8,
|
7848
7848
|
angle = 0,
|
7849
7849
|
vertexColor="black",
|
7850
|
+
vertexColorKey=None,
|
7850
7851
|
vertexSize=6,
|
7852
|
+
vertexSizeKey=None,
|
7851
7853
|
vertexLabelKey=None,
|
7852
7854
|
vertexGroupKey=None,
|
7853
7855
|
vertexGroups=[],
|
7854
7856
|
showVertices=True,
|
7855
|
-
|
7857
|
+
showVertexLabel=False,
|
7856
7858
|
showVertexLegend=False,
|
7857
7859
|
edgeColor="black",
|
7860
|
+
edgeColorKey=None,
|
7858
7861
|
edgeWidth=1,
|
7862
|
+
edgeWidthKey=None,
|
7859
7863
|
edgeLabelKey=None,
|
7860
7864
|
edgeGroupKey=None,
|
7861
7865
|
edgeGroups=[],
|
7862
7866
|
showEdges=True,
|
7863
|
-
|
7867
|
+
showEdgeLabel=False,
|
7864
7868
|
showEdgeLegend=False,
|
7865
7869
|
colorScale='viridis',
|
7866
7870
|
renderer=None,
|
@@ -7904,8 +7908,12 @@ class Graph:
|
|
7904
7908
|
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
|
7905
7909
|
- A named CSS color.
|
7906
7910
|
The default is "black".
|
7911
|
+
vertexColorKey : str , optional
|
7912
|
+
The dictionary key under which to find the vertex color. The default is None.
|
7907
7913
|
vertexSize : float , optional
|
7908
7914
|
The desired size of the vertices. The default is 1.1.
|
7915
|
+
vertexSizeKey : str , optional
|
7916
|
+
The dictionary key under which to find the vertex size. The default is None.
|
7909
7917
|
vertexLabelKey : str , optional
|
7910
7918
|
The dictionary key to use to display the vertex label. The default is None.
|
7911
7919
|
vertexGroupKey : str , optional
|
@@ -7914,7 +7922,7 @@ class Graph:
|
|
7914
7922
|
The list of vertex groups against which to index the color of the vertex. The default is [].
|
7915
7923
|
showVertices : bool , optional
|
7916
7924
|
If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
|
7917
|
-
|
7925
|
+
showVertexLabel : bool , optional
|
7918
7926
|
If set to True, the vertex labels are shown permenantely on screen. Otherwise, they are not. The default is False.
|
7919
7927
|
showVertexLegend : bool , optional
|
7920
7928
|
If set to True the vertex legend will be drawn. Otherwise, it will not be drawn. The default is False.
|
@@ -7926,8 +7934,12 @@ class Graph:
|
|
7926
7934
|
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
|
7927
7935
|
- A named CSS color.
|
7928
7936
|
The default is "black".
|
7937
|
+
edgeColorKey : str , optional
|
7938
|
+
The dictionary key under which to find the edge color. The default is None.
|
7929
7939
|
edgeWidth : float , optional
|
7930
7940
|
The desired thickness of the output edges. The default is 1.
|
7941
|
+
edgeWidthKey : str , optional
|
7942
|
+
The dictionary key under which to find the edge width. The default is None.
|
7931
7943
|
edgeLabelKey : str , optional
|
7932
7944
|
The dictionary key to use to display the edge label. The default is None.
|
7933
7945
|
edgeGroupKey : str , optional
|
@@ -7936,7 +7948,7 @@ class Graph:
|
|
7936
7948
|
The list of edge groups against which to index the color of the edge. The default is [].
|
7937
7949
|
showEdges : bool , optional
|
7938
7950
|
If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
|
7939
|
-
|
7951
|
+
showEdgeLabel : bool , optional
|
7940
7952
|
If set to True, the edge labels are shown permenantely on screen. Otherwise, they are not. The default is False.
|
7941
7953
|
showEdgeLegend : bool , optional
|
7942
7954
|
If set to True the edge legend will be drawn. Otherwise, it will not be drawn. The default is False.
|
@@ -7997,7 +8009,7 @@ class Graph:
|
|
7997
8009
|
print("Graph.Show - Error: The input graph is not a valid graph. Returning None.")
|
7998
8010
|
return None
|
7999
8011
|
|
8000
|
-
data= Plotly.DataByGraph(graph, sagitta=sagitta, absolute=absolute, sides=sides, angle=angle, vertexColor=vertexColor, vertexSize=vertexSize, vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, showVertices=showVertices,
|
8012
|
+
data= Plotly.DataByGraph(graph, sagitta=sagitta, absolute=absolute, sides=sides, angle=angle, vertexColor=vertexColor, vertexColorKey=vertexColorKey, vertexSize=vertexSize, vertexSizeKey=vertexSizeKey, vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, showVertices=showVertices, showVertexLabel=showVertexLabel, showVertexLegend=showVertexLegend, edgeColor=edgeColor, edgeColorKey=edgeColorKey, edgeWidth=edgeWidth, edgeWidthKey=edgeWidthKey, edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups, showEdges=showEdges, showEdgeLabel=showEdgeLabel, showEdgeLegend=showEdgeLegend, colorScale=colorScale, silent=silent)
|
8001
8013
|
fig = Plotly.FigureByData(data, width=width, height=height, xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize, backgroundColor=backgroundColor,
|
8002
8014
|
marginLeft=marginLeft, marginRight=marginRight, marginTop=marginTop, marginBottom=marginBottom, tolerance=tolerance)
|
8003
8015
|
Plotly.Show(fig, renderer=renderer, camera=camera, center=center, up=up, projection=projection)
|
topologicpy/Helper.py
CHANGED
@@ -95,6 +95,117 @@ class Helper:
|
|
95
95
|
|
96
96
|
return closest_index
|
97
97
|
|
98
|
+
@staticmethod
|
99
|
+
def ClusterByKeys(elements, dictionaries, *keys, silent=False):
|
100
|
+
"""
|
101
|
+
Clusters the input list of elements and dictionaries based on the input key or keys.
|
102
|
+
|
103
|
+
Parameters
|
104
|
+
----------
|
105
|
+
elements : list
|
106
|
+
The input list of elements to be clustered.
|
107
|
+
dictionaries : list[Topology.Dictionary]
|
108
|
+
The input list of dictionaries to be consulted for clustering. This is assumed to be in the same order as the list of elements.
|
109
|
+
keys : str or list or comma-separated str input parameters
|
110
|
+
The key or keys in the topology's dictionary to use for clustering.
|
111
|
+
silent : bool , optional
|
112
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
113
|
+
|
114
|
+
|
115
|
+
Returns
|
116
|
+
-------
|
117
|
+
dict
|
118
|
+
A dictionary containing the elements and the dictionaries, but clustered. The dictionary has two keys:
|
119
|
+
"elements": list
|
120
|
+
A nested list of elements where each item is a list of elements with the same key values.
|
121
|
+
"dictionaries": list
|
122
|
+
A nested list of dictionaries where each item is a list of dictionaries with the same key values.
|
123
|
+
"""
|
124
|
+
|
125
|
+
from topologicpy.Dictionary import Dictionary
|
126
|
+
from topologicpy.Helper import Helper
|
127
|
+
import inspect
|
128
|
+
|
129
|
+
keys_list = list(keys)
|
130
|
+
keys_list = Helper.Flatten(keys_list)
|
131
|
+
keys_list = [x for x in keys_list if isinstance(x, str)]
|
132
|
+
|
133
|
+
if len(keys_list) == 0:
|
134
|
+
if not silent:
|
135
|
+
print("Helper.ClusterByKeys - Error: The input keys parameter is an empty list. Returning None.")
|
136
|
+
curframe = inspect.currentframe()
|
137
|
+
calframe = inspect.getouterframes(curframe, 2)
|
138
|
+
print('caller name:', calframe[1][3])
|
139
|
+
return None
|
140
|
+
|
141
|
+
if len(keys_list) == 0:
|
142
|
+
if not silent:
|
143
|
+
print("Helper.ClusterByKeys - Error: The input keys parameter does not contain any valid strings. Returning None.")
|
144
|
+
curframe = inspect.currentframe()
|
145
|
+
calframe = inspect.getouterframes(curframe, 2)
|
146
|
+
print('caller name:', calframe[1][3])
|
147
|
+
return None
|
148
|
+
if not (len(elements) == len(dictionaries)):
|
149
|
+
if not silent:
|
150
|
+
print("Helper.ClusterByKeys - Error: The input elements parameter does not have the same length as the input dictionaries parameter. Returning None.")
|
151
|
+
return None
|
152
|
+
|
153
|
+
elements_clusters = []
|
154
|
+
dict_clusters = []
|
155
|
+
values = []
|
156
|
+
new_dictionaries = []
|
157
|
+
for i, d in enumerate(dictionaries):
|
158
|
+
element = elements[i]
|
159
|
+
|
160
|
+
d_keys = Dictionary.Keys(d)
|
161
|
+
if len(d_keys) > 0:
|
162
|
+
values_list = []
|
163
|
+
for key in keys_list:
|
164
|
+
v = Dictionary.ValueAtKey(d, key)
|
165
|
+
if not v == None:
|
166
|
+
values_list.append(v)
|
167
|
+
values_list = str(values_list)
|
168
|
+
d = Dictionary.SetValueAtKey(d, "_clustering_key_", values_list)
|
169
|
+
new_dictionaries.append(d)
|
170
|
+
values.append(values_list)
|
171
|
+
|
172
|
+
values = list(set(values))
|
173
|
+
remaining_dictionaries = [x for x in new_dictionaries]
|
174
|
+
remaining_elements = [x for x in elements]
|
175
|
+
remaining_indices = [i for i, x in enumerate(elements)]
|
176
|
+
|
177
|
+
if len(values) == 0:
|
178
|
+
return {"elements": [elements], "dictionaries": [dictionaries]}
|
179
|
+
for value in values:
|
180
|
+
if len(remaining_dictionaries) == 0:
|
181
|
+
break
|
182
|
+
dict = Dictionary.Filter(remaining_elements, remaining_dictionaries, searchType="equal to", key="_clustering_key_", value=value)
|
183
|
+
filtered_indices = dict['filteredIndices']
|
184
|
+
final_dictionaries = []
|
185
|
+
final_elements = []
|
186
|
+
if len(filtered_indices) > 0:
|
187
|
+
for filtered_index in filtered_indices:
|
188
|
+
filtered_dictionary = remaining_dictionaries[filtered_index]
|
189
|
+
filtered_dictionary = Dictionary.RemoveKey(filtered_dictionary, "_clustering_key_")
|
190
|
+
final_dictionaries.append(filtered_dictionary)
|
191
|
+
filtered_element = remaining_elements[filtered_index]
|
192
|
+
final_elements.append(filtered_element)
|
193
|
+
dict_clusters.append(final_dictionaries)
|
194
|
+
elements_clusters.append(final_elements)
|
195
|
+
remaining_dictionaries = dict['otherDictionaries']
|
196
|
+
remaining_elements = dict['otherElements']
|
197
|
+
remaining_indices = dict['otherIndices']
|
198
|
+
if len(remaining_elements) > 0:
|
199
|
+
temp_dict_cluster = []
|
200
|
+
temp_element_cluster = []
|
201
|
+
for remaining_index in remaining_indices:
|
202
|
+
temp_element_cluster.append(remaining_elements[remaining_index])
|
203
|
+
temp_dict_cluster.append(remaining_dictionaries[remaining_index])
|
204
|
+
if len(temp_element_cluster) > 0:
|
205
|
+
dict_clusters.append(temp_dict_cluster)
|
206
|
+
elements_clusters.append(temp_element_cluster)
|
207
|
+
return {"elements": elements_clusters, "dictionaries": dict_clusters}
|
208
|
+
|
98
209
|
@staticmethod
|
99
210
|
def Flatten(listA):
|
100
211
|
"""
|