topologicpy 0.8.39__py3-none-any.whl → 0.8.41__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/Color.py +74 -1
- topologicpy/Graph.py +601 -61
- topologicpy/Plotly.py +80 -6
- topologicpy/Topology.py +16 -2
- topologicpy/Vertex.py +38 -31
- topologicpy/version.py +1 -1
- {topologicpy-0.8.39.dist-info → topologicpy-0.8.41.dist-info}/METADATA +1 -1
- {topologicpy-0.8.39.dist-info → topologicpy-0.8.41.dist-info}/RECORD +11 -11
- {topologicpy-0.8.39.dist-info → topologicpy-0.8.41.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.39.dist-info → topologicpy-0.8.41.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.39.dist-info → topologicpy-0.8.41.dist-info}/top_level.txt +0 -0
topologicpy/Color.py
CHANGED
@@ -55,7 +55,80 @@ class Color:
|
|
55
55
|
|
56
56
|
return return_hex.upper()
|
57
57
|
|
58
|
-
|
58
|
+
@staticmethod
|
59
|
+
def AverageHex(*colors, silent: bool = False):
|
60
|
+
"""
|
61
|
+
Averages the input list of hex colors.
|
62
|
+
|
63
|
+
Parameters
|
64
|
+
----------
|
65
|
+
colors : *list or str
|
66
|
+
The input color parameter which can be any of RGB, CMYK, CSS Named Color, or Hex
|
67
|
+
|
68
|
+
Returns
|
69
|
+
-------
|
70
|
+
str
|
71
|
+
A hexadecimal color string in the format '#RRGGBB'.
|
72
|
+
"""
|
73
|
+
from topologicpy.Helper import Helper
|
74
|
+
import inspect
|
75
|
+
|
76
|
+
def avg(hex1, hex2):
|
77
|
+
# Remove leading "#" if present
|
78
|
+
hex1 = hex1.lstrip('#')
|
79
|
+
hex2 = hex2.lstrip('#')
|
80
|
+
|
81
|
+
# Convert to RGB components
|
82
|
+
r1, g1, b1 = int(hex1[0:2], 16), int(hex1[2:4], 16), int(hex1[4:6], 16)
|
83
|
+
r2, g2, b2 = int(hex2[0:2], 16), int(hex2[2:4], 16), int(hex2[4:6], 16)
|
84
|
+
|
85
|
+
# Compute average for each channel
|
86
|
+
r_avg = (r1 + r2) // 2
|
87
|
+
g_avg = (g1 + g2) // 2
|
88
|
+
b_avg = (b1 + b2) // 2
|
89
|
+
|
90
|
+
# Return as hex string
|
91
|
+
return f"#{r_avg:02X}{g_avg:02X}{b_avg:02X}"
|
92
|
+
|
93
|
+
if len(colors) == 0:
|
94
|
+
if not silent:
|
95
|
+
print("Color.AverageColors - Error: The input colors parameter is an empty list. Returning None.")
|
96
|
+
curframe = inspect.currentframe()
|
97
|
+
calframe = inspect.getouterframes(curframe, 2)
|
98
|
+
print('caller name:', calframe[1][3])
|
99
|
+
return None
|
100
|
+
if len(colors) == 1:
|
101
|
+
colorList = colors[0]
|
102
|
+
if isinstance(colorList, list):
|
103
|
+
if len(colorList) == 0:
|
104
|
+
if not silent:
|
105
|
+
print("Color.AverageHex - Error: The input colors parameter is an empty list. Returning None.")
|
106
|
+
curframe = inspect.currentframe()
|
107
|
+
calframe = inspect.getouterframes(curframe, 2)
|
108
|
+
print('caller name:', calframe[1][3])
|
109
|
+
return None
|
110
|
+
else:
|
111
|
+
if not silent:
|
112
|
+
print("Color.AverageHex - Warning: The input colors parameter contains only one color. Returning the same topology.")
|
113
|
+
curframe = inspect.currentframe()
|
114
|
+
calframe = inspect.getouterframes(curframe, 2)
|
115
|
+
print('caller name:', calframe[1][3])
|
116
|
+
return colorList
|
117
|
+
else:
|
118
|
+
colorList = Helper.Flatten(list(colors))
|
119
|
+
colorList = [x for x in colorList if isinstance(x, str)]
|
120
|
+
if len(colorList) == 0:
|
121
|
+
if not silent:
|
122
|
+
print("Color.AverageHex - Error: The input parameters do not contain any valid colors. Returning None.")
|
123
|
+
curframe = inspect.currentframe()
|
124
|
+
calframe = inspect.getouterframes(curframe, 2)
|
125
|
+
print('caller name:', calframe[1][3])
|
126
|
+
return None
|
127
|
+
final_color = Color.AnyToHex(colorList[0])
|
128
|
+
for clr in colorList[1:]:
|
129
|
+
final_color = avg(final_color, Color.AnyToHex(clr))
|
130
|
+
return final_color
|
131
|
+
|
59
132
|
@staticmethod
|
60
133
|
def ByCSSNamedColor(color, alpha: float = None):
|
61
134
|
"""
|
topologicpy/Graph.py
CHANGED
@@ -7751,6 +7751,34 @@ class Graph:
|
|
7751
7751
|
edge = Topology.SetDictionary(edge, d)
|
7752
7752
|
return graph
|
7753
7753
|
|
7754
|
+
@staticmethod
|
7755
|
+
def IsEmpty(graph, silent: bool = False):
|
7756
|
+
"""
|
7757
|
+
Tests if the input graph is empty (Has no vertices).
|
7758
|
+
|
7759
|
+
Parameters
|
7760
|
+
----------
|
7761
|
+
graph : topologic_core.Graph
|
7762
|
+
The input graph.
|
7763
|
+
silent : bool , optional
|
7764
|
+
If set to True, error and warning messages are suppressed. The default is False.
|
7765
|
+
|
7766
|
+
Returns
|
7767
|
+
-------
|
7768
|
+
bool
|
7769
|
+
True if the two input graphs are isomorphic. False otherwise
|
7770
|
+
|
7771
|
+
"""
|
7772
|
+
|
7773
|
+
from topologicpy.Topology import Topology
|
7774
|
+
|
7775
|
+
if not Topology.IsInstance(graph, "Graph"):
|
7776
|
+
if not silent:
|
7777
|
+
print("Graph.IsEmpty - Error: The input graph parameter is not a valid graph. Returning None.")
|
7778
|
+
return None
|
7779
|
+
|
7780
|
+
return (len(Graph.Vertices(graph)) == 0)
|
7781
|
+
|
7754
7782
|
@staticmethod
|
7755
7783
|
def IsIsomorphic(graphA, graphB, maxIterations=10, silent=False):
|
7756
7784
|
"""
|
@@ -9192,6 +9220,142 @@ class Graph:
|
|
9192
9220
|
return None
|
9193
9221
|
return graph.GetGUID()
|
9194
9222
|
|
9223
|
+
@staticmethod
|
9224
|
+
def HasseDiagram(topology, types=["vertex", "edge", "wire", "face", "shell", "cell", "cellComplex"], topDown: bool = False, minDistance: float=0.1, vertexLabelKey: str="label", vertexTypeKey: str="type", vertexColorKey: str="color", colorScale: str="viridis", storeBREP: bool = False, tolerance: float=0.0001, silent: bool=False):
|
9225
|
+
"""
|
9226
|
+
Constructs a Hasse diagram from the input topology as a directed graph. See: https://en.wikipedia.org/wiki/Hasse_diagram
|
9227
|
+
Vertices represent topologies (vertices, edges, wires, faces, shells, cells, cellComplexes).
|
9228
|
+
Edges represent inclusion (e.g. vertex ⊂ edge, edge ⊂ wire).
|
9229
|
+
|
9230
|
+
Parameters
|
9231
|
+
----------
|
9232
|
+
topology : topologic_core.Topology
|
9233
|
+
The input topology
|
9234
|
+
types : optional, list
|
9235
|
+
The list of topology types that you wish to encode in the Hasse diagram.
|
9236
|
+
This list must be ordered according to topologic_core's class hierarchy.
|
9237
|
+
If you are not interested in representing some topology types. These can be omitted.
|
9238
|
+
The default is:
|
9239
|
+
["vertex", "edge", "wire", "face", "shell", "cell", "cellComplex"].
|
9240
|
+
topDown : bool , optional
|
9241
|
+
If set to True, the graph edges are directed from topologies to their subtopologies.
|
9242
|
+
Otherwise, they are directed from topologies to their supertopologies. The default is False.
|
9243
|
+
minDistance : float , optional
|
9244
|
+
The desired minimum distance between the vertices of the graph. The default is 0.1.
|
9245
|
+
vertexLabelKey: str , optional
|
9246
|
+
The desired vertex dictionary key under which to store a unique label (of the form Type_Index). The default is "label".
|
9247
|
+
vertexTypeKey: str , optional
|
9248
|
+
The desired vertex dictionary key under which to store the topology type (e.g. "vertex", "edge", "wire"). The default is "type".
|
9249
|
+
vertexColorKey: str , optional
|
9250
|
+
The desired vertex dictionary key under which to store the topology color. The default is "color".
|
9251
|
+
colorScale : str , optional
|
9252
|
+
The desired type of plotly color scales to use (e.g. "viridis", "plasma"). The default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
|
9253
|
+
In addition to these, three color-blind friendly scales are included. These are "protanopia", "deuteranopia", and "tritanopia" for red, green, and blue colorblindness respectively.
|
9254
|
+
storeBREP : bool , optional
|
9255
|
+
If set to True, store the BRep of the topology in its representative vertex. The default is False.
|
9256
|
+
tolerance : float
|
9257
|
+
The desired tolerance. The default is 0.0001.
|
9258
|
+
silent : bool , optional
|
9259
|
+
If set to True, error and warning messages are suppressed. The default is False.
|
9260
|
+
|
9261
|
+
Returns
|
9262
|
+
-------
|
9263
|
+
topologic_core.Graph
|
9264
|
+
The created Hesse diagram graph.
|
9265
|
+
|
9266
|
+
"""
|
9267
|
+
from topologicpy.Vertex import Vertex
|
9268
|
+
from topologicpy.Edge import Edge
|
9269
|
+
from topologicpy.Dictionary import Dictionary
|
9270
|
+
from topologicpy.Color import Color
|
9271
|
+
from topologicpy.Topology import Topology
|
9272
|
+
|
9273
|
+
def label(topo, index):
|
9274
|
+
cls = Topology.TypeAsString(topo)
|
9275
|
+
return f"{cls}_{index}"
|
9276
|
+
|
9277
|
+
def collect_topologies(topology, topo_types):
|
9278
|
+
"""
|
9279
|
+
Returns a dict of all sub-topologies by dimension.
|
9280
|
+
"""
|
9281
|
+
topo_by_type = {}
|
9282
|
+
for sub_type in topo_types:
|
9283
|
+
topo_by_type[sub_type] = Topology.SubTopologies(topology, subTopologyType=sub_type)
|
9284
|
+
return topo_by_type
|
9285
|
+
|
9286
|
+
if not Topology.IsInstance(topology, "topology"):
|
9287
|
+
if not silent:
|
9288
|
+
print("Graph.HasseDiagram - Error: The input topology parameter is not a valid topology. Returning None.")
|
9289
|
+
return None
|
9290
|
+
if minDistance <= tolerance:
|
9291
|
+
if not silent:
|
9292
|
+
print("Graph.HasseDiagram - Error: The input minDistance parameter cannot be less than the input tolerance parameter. Returning None.")
|
9293
|
+
return None
|
9294
|
+
types = [t.lower() for t in types]
|
9295
|
+
for type in types:
|
9296
|
+
if type not in ["vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex"]:
|
9297
|
+
if not silent:
|
9298
|
+
print("Graph.HasseDiagram - Error: Unknown type found in the types input parameter. Returning None.")
|
9299
|
+
return None
|
9300
|
+
|
9301
|
+
topology_type = Topology.TypeAsString(topology).lower()
|
9302
|
+
try:
|
9303
|
+
sub_types = types[:types.index(topology_type)]
|
9304
|
+
except:
|
9305
|
+
sub_types = types
|
9306
|
+
topo_by_type = collect_topologies(topology, sub_types)
|
9307
|
+
all_topos = []
|
9308
|
+
topo_ids = {}
|
9309
|
+
index = 0
|
9310
|
+
|
9311
|
+
# Flatten and assign unique labels
|
9312
|
+
for sub_type in sub_types:
|
9313
|
+
color = Color.AnyToHex(Color.ByValueInRange(float(types.index(sub_type)), minValue=0, maxValue=6, colorScale=colorScale))
|
9314
|
+
lbl_index = 1
|
9315
|
+
for t in topo_by_type[sub_type]:
|
9316
|
+
lbl = label(t, lbl_index)
|
9317
|
+
d = Topology.Dictionary(t)
|
9318
|
+
d = Dictionary.SetValuesAtKeys(d, [vertexLabelKey, vertexTypeKey, vertexColorKey], [lbl, sub_type, color])
|
9319
|
+
t = Topology.SetDictionary(t, d)
|
9320
|
+
all_topos.append(t)
|
9321
|
+
topo_ids[lbl] = index
|
9322
|
+
index += 1
|
9323
|
+
lbl_index += 1
|
9324
|
+
|
9325
|
+
# Create graph vertices
|
9326
|
+
graph_vertices = [Topology.Centroid(_) for _ in all_topos]
|
9327
|
+
|
9328
|
+
# Add dictionaries to each vertex
|
9329
|
+
for i, t in enumerate(all_topos):
|
9330
|
+
d = Topology.Dictionary(t)
|
9331
|
+
if storeBREP == True:
|
9332
|
+
d = Dictionary.SetValueAtKey(d,"brep", Topology.BREPString(t))
|
9333
|
+
graph_vertices[i] = Topology.SetDictionary(graph_vertices[i], d)
|
9334
|
+
|
9335
|
+
graph_vertices = Vertex.Separate(graph_vertices, minDistance= minDistance, tolerance=tolerance)
|
9336
|
+
# Build edges of Hasse diagram
|
9337
|
+
graph_edges = []
|
9338
|
+
for parent_type in sub_types[1:]:
|
9339
|
+
for parent in topo_by_type[parent_type]:
|
9340
|
+
parent_label = Dictionary.ValueAtKey(Topology.Dictionary(parent), vertexLabelKey)
|
9341
|
+
children = Topology.SubTopologies(parent, subTopologyType=types[types.index(parent_type) - 1])
|
9342
|
+
for child in children:
|
9343
|
+
child_label = Dictionary.ValueAtKey(Topology.Dictionary(child), vertexLabelKey)
|
9344
|
+
child_id = topo_ids.get(child_label)
|
9345
|
+
parent_id = topo_ids.get(parent_label)
|
9346
|
+
if child_id is not None and parent_id is not None:
|
9347
|
+
if topDown:
|
9348
|
+
sv = graph_vertices[parent_id]
|
9349
|
+
ev = graph_vertices[child_id]
|
9350
|
+
else:
|
9351
|
+
sv = graph_vertices[child_id]
|
9352
|
+
ev = graph_vertices[parent_id]
|
9353
|
+
graph_edges.append(Edge.ByVertices(sv, ev, tolerance=tolerance, silent=silent))
|
9354
|
+
|
9355
|
+
return_graph = Graph.ByVerticesEdges(graph_vertices, graph_edges)
|
9356
|
+
return_graph = Graph.SetDictionary(return_graph, Topology.Dictionary(topology))
|
9357
|
+
return return_graph
|
9358
|
+
|
9195
9359
|
@staticmethod
|
9196
9360
|
def IncomingEdges(graph, vertex, directed: bool = False, tolerance: float = 0.0001) -> list:
|
9197
9361
|
"""
|
@@ -12914,38 +13078,49 @@ class Graph:
|
|
12914
13078
|
return round(numerator / denominator, mantissa) if denominator != 0 else 0.0
|
12915
13079
|
|
12916
13080
|
@staticmethod
|
12917
|
-
def _vertex_is_same(v1, v2,
|
13081
|
+
def _vertex_is_same(v1, v2, keys=None, tolerance=0.0001):
|
13082
|
+
from topologicpy.Vertex import Vertex
|
12918
13083
|
from topologicpy.Topology import Topology
|
12919
13084
|
from topologicpy.Dictionary import Dictionary
|
13085
|
+
|
13086
|
+
if keys == None or keys == [] or keys == "":
|
13087
|
+
return Vertex.Distance(v1, v2) <= tolerance
|
13088
|
+
|
13089
|
+
if isinstance(keys, str):
|
13090
|
+
if "loc" in keys.lower() or "coord" in keys.lower() or "xyz" in keys.lower():
|
13091
|
+
return Vertex.Distance(v1, v2) <= tolerance
|
13092
|
+
if not isinstance(keys, list):
|
13093
|
+
keys = [keys]
|
13094
|
+
|
12920
13095
|
d1 = Topology.Dictionary(v1)
|
12921
13096
|
d2 = Topology.Dictionary(v2)
|
12922
|
-
a = Dictionary.ValueAtKey(d1,
|
12923
|
-
b = Dictionary.ValueAtKey(d2,
|
13097
|
+
a = [Dictionary.ValueAtKey(d1, k, "0") for k in keys]
|
13098
|
+
b = [Dictionary.ValueAtKey(d2, k, "1") for k in keys]
|
12924
13099
|
return a == b
|
12925
13100
|
|
12926
13101
|
@staticmethod
|
12927
|
-
def _vertex_in_list(vertex, vertex_list,
|
13102
|
+
def _vertex_in_list(vertex, vertex_list, keys=None, tolerance=0.0001):
|
12928
13103
|
for i, v1 in enumerate(vertex_list):
|
12929
|
-
if Graph._vertex_is_same(vertex, v1,
|
13104
|
+
if Graph._vertex_is_same(vertex, v1, keys=keys, tolerance=tolerance):
|
12930
13105
|
return i+1
|
12931
13106
|
return False
|
12932
13107
|
|
12933
13108
|
@staticmethod
|
12934
|
-
def _edge_in_list(edge, edge_list, vertices_a, vertices_b,
|
13109
|
+
def _edge_in_list(edge, edge_list, vertices_a, vertices_b, keys=None, tolerance=0.0001):
|
12935
13110
|
sv1 = vertices_a[edge[0]]
|
12936
13111
|
ev1 = vertices_a[edge[1]]
|
12937
13112
|
for i, e in enumerate(edge_list):
|
12938
13113
|
sv2 = vertices_b[e[0]]
|
12939
13114
|
ev2 = vertices_b[e[1]]
|
12940
|
-
if (Graph._vertex_is_same(sv1, sv2,
|
12941
|
-
(Graph._vertex_is_same(sv1, ev2,
|
13115
|
+
if (Graph._vertex_is_same(sv1, sv2, keys=keys, tolerance=tolerance) and Graph._vertex_is_same(ev1, ev2, keys=keys, tolerance=tolerance)) or \
|
13116
|
+
(Graph._vertex_is_same(sv1, ev2, keys=keys, tolerance=tolerance) and Graph._vertex_is_same(ev1, sv2, keys=keys, tolerance=tolerance)):
|
12942
13117
|
return i+1
|
12943
13118
|
return False
|
12944
13119
|
|
12945
13120
|
@staticmethod
|
12946
|
-
def Union(graphA, graphB,
|
13121
|
+
def Union(graphA, graphB, vertexKeys=None, useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
|
12947
13122
|
"""
|
12948
|
-
Union the two input graphs based on
|
13123
|
+
Union the two input graphs based on the input vertex keys. See https://en.wikipedia.org/wiki/Boolean_operation.
|
12949
13124
|
|
12950
13125
|
Parameters
|
12951
13126
|
----------
|
@@ -12953,11 +13128,18 @@ class Graph:
|
|
12953
13128
|
The first input graph.
|
12954
13129
|
graphB : topologic_core.Graph
|
12955
13130
|
The second input graph.
|
12956
|
-
|
12957
|
-
The vertex dictionary key to use to determine if two vertices are the same.
|
13131
|
+
vertexKeys : list or str , optional
|
13132
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13133
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13134
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13135
|
+
useCentroid : bool , optional
|
13136
|
+
If set to True, the coordinates of identical vertices from each graph are averaged to located the new merged vertex of the resulting graph.
|
13137
|
+
Otherwise, the coordinates of the vertex of the first input graph are used. The default is False.
|
13138
|
+
tolerance : float , optional
|
13139
|
+
The desired tolerance. The default is 0.0001.
|
12958
13140
|
silent : bool , optional
|
12959
13141
|
If set to True, error and warning messages are suppressed. The default is False.
|
12960
|
-
|
13142
|
+
|
12961
13143
|
Returns
|
12962
13144
|
-------
|
12963
13145
|
topologic_core.Graph
|
@@ -12968,6 +13150,7 @@ class Graph:
|
|
12968
13150
|
from topologicpy.Edge import Edge
|
12969
13151
|
from topologicpy.Topology import Topology
|
12970
13152
|
from topologicpy.Dictionary import Dictionary
|
13153
|
+
from topologicpy.Cluster import Cluster
|
12971
13154
|
|
12972
13155
|
if not Topology.IsInstance(graphA, "graph"):
|
12973
13156
|
if not silent:
|
@@ -12977,10 +13160,6 @@ class Graph:
|
|
12977
13160
|
if not silent:
|
12978
13161
|
print("Graph.Union - Error: The graphB input parameter is not a valid graph. Returning None.")
|
12979
13162
|
return None
|
12980
|
-
if not isinstance(vertexKey, str):
|
12981
|
-
if not silent:
|
12982
|
-
print("Graph.Union - Error: The vertexKey input parameter is not a valid string. Returning None.")
|
12983
|
-
return None
|
12984
13163
|
vertices_a = Graph.Vertices(graphA)
|
12985
13164
|
vertices_a_new = []
|
12986
13165
|
for v in vertices_a:
|
@@ -13008,11 +13187,16 @@ class Graph:
|
|
13008
13187
|
|
13009
13188
|
def _add_vertex(v):
|
13010
13189
|
for i, uv in enumerate(union_vertices):
|
13011
|
-
if Graph._vertex_is_same(v, uv,
|
13190
|
+
if Graph._vertex_is_same(v, uv, keys=vertexKeys):
|
13012
13191
|
d_a = Topology.Dictionary(v)
|
13013
13192
|
d_b = Topology.Dictionary(uv)
|
13014
13193
|
d_c = Dictionary.ByMergedDictionaries(d_a, d_b)
|
13015
|
-
|
13194
|
+
if useCentroid:
|
13195
|
+
c = Topology.Centroid(Cluster.ByTopologies([v, uv]))
|
13196
|
+
else:
|
13197
|
+
c = uv
|
13198
|
+
c = Topology.SetDictionary(c, d_c)
|
13199
|
+
union_vertices[i] = c
|
13016
13200
|
return i
|
13017
13201
|
union_vertices.append(v)
|
13018
13202
|
return len(union_vertices) - 1
|
@@ -13029,8 +13213,8 @@ class Graph:
|
|
13029
13213
|
for k, e in enumerate(union_edges):
|
13030
13214
|
svi = Edge.StartVertex(e)
|
13031
13215
|
evi = Edge.EndVertex(e)
|
13032
|
-
if (Graph._vertex_is_same(svi, vi,
|
13033
|
-
(Graph._vertex_is_same(svi, vj,
|
13216
|
+
if (Graph._vertex_is_same(svi, vi, keys=vertexKeys, tolerance=tolerance) and Graph._vertex_is_same(evi, vj, keys=vertexKeys, tolerance=tolerance)) or \
|
13217
|
+
(Graph._vertex_is_same(svi, vj, keys=vertexKeys, tolerance=tolerance) and Graph._vertex_is_same(evi, vi, keys=vertexKeys, tolerance=tolerance)):
|
13034
13218
|
# Merge dictionaries
|
13035
13219
|
d_a = Topology.Dictionary(e)
|
13036
13220
|
d_c = Dictionary.ByMergedDictionaries([d_a, dictionary], silent=True)
|
@@ -13047,20 +13231,145 @@ class Graph:
|
|
13047
13231
|
for idx, e in enumerate(edges_a):
|
13048
13232
|
i = index_map_a[e[0]]
|
13049
13233
|
j = index_map_a[e[1]]
|
13050
|
-
|
13234
|
+
if not i == j:
|
13235
|
+
_add_edge(i, j, Dictionary.ByPythonDictionary(edges_a_dicts[idx]))
|
13051
13236
|
|
13052
13237
|
# Add edges from B, merging duplicates
|
13053
13238
|
for idx, e in enumerate(edges_b):
|
13054
13239
|
i = index_map_b[e[0]]
|
13055
13240
|
j = index_map_b[e[1]]
|
13056
|
-
|
13241
|
+
if not i == j:
|
13242
|
+
_add_edge(i, j, Dictionary.ByPythonDictionary(edges_b_dicts[idx]))
|
13057
13243
|
|
13058
13244
|
return Graph.ByVerticesEdges(union_vertices, union_edges)
|
13245
|
+
|
13246
|
+
@staticmethod
|
13247
|
+
def Impose(graphA, graphB, vertexKeys=None, useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
|
13248
|
+
"""
|
13249
|
+
Imposes the second input graph on the first input graph based on the input vertex keys. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13250
|
+
|
13251
|
+
Parameters
|
13252
|
+
----------
|
13253
|
+
graphA : topologic_core.Graph
|
13254
|
+
The first input graph.
|
13255
|
+
graphB : topologic_core.Graph
|
13256
|
+
The second input graph.
|
13257
|
+
vertexKeys : list or str , optional
|
13258
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13259
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13260
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13261
|
+
useCentroid : bool , optional
|
13262
|
+
If set to True, the coordinates of identical vertices from each graph are averaged to located the new merged vertex of the resulting graph.
|
13263
|
+
Otherwise, the coordinates of the vertex of the second input graph are used. The default is False.
|
13264
|
+
tolerance : float , optional
|
13265
|
+
The desired tolerance. The default is 0.0001.
|
13266
|
+
silent : bool , optional
|
13267
|
+
If set to True, error and warning messages are suppressed. The default is False.
|
13268
|
+
|
13269
|
+
|
13270
|
+
Returns
|
13271
|
+
-------
|
13272
|
+
topologic_core.Graph
|
13273
|
+
the resultant graph. Vertex and edge dictionaries are merged.
|
13274
|
+
|
13275
|
+
"""
|
13276
|
+
from topologicpy.Vertex import Vertex
|
13277
|
+
from topologicpy.Edge import Edge
|
13278
|
+
from topologicpy.Topology import Topology
|
13279
|
+
from topologicpy.Dictionary import Dictionary
|
13280
|
+
from topologicpy.Cluster import Cluster
|
13281
|
+
|
13282
|
+
if not Topology.IsInstance(graphA, "graph"):
|
13283
|
+
if not silent:
|
13284
|
+
print("Graph.Impose - Error: The graphA input parameter is not a valid graph. Returning None.")
|
13285
|
+
return None
|
13286
|
+
if not Topology.IsInstance(graphB, "graph"):
|
13287
|
+
if not silent:
|
13288
|
+
print("Graph.Impose - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13289
|
+
return None
|
13290
|
+
vertices_a = Graph.Vertices(graphA)
|
13291
|
+
vertices_a_new = []
|
13292
|
+
for v in vertices_a:
|
13293
|
+
d = Topology.Dictionary(v)
|
13294
|
+
v_new = Vertex.ByCoordinates(Vertex.Coordinates(v))
|
13295
|
+
v_new = Topology.SetDictionary(v_new, d)
|
13296
|
+
vertices_a_new.append(v_new)
|
13297
|
+
vertices_a = vertices_a_new
|
13298
|
+
vertices_b = Graph.Vertices(graphB)
|
13299
|
+
vertices_b_new = []
|
13300
|
+
for v in vertices_b:
|
13301
|
+
d = Topology.Dictionary(v)
|
13302
|
+
v_new = Vertex.ByCoordinates(Vertex.Coordinates(v))
|
13303
|
+
v_new = Topology.SetDictionary(v_new, d)
|
13304
|
+
vertices_b_new.append(v_new)
|
13305
|
+
vertices_b = vertices_b_new
|
13306
|
+
mesh_data_a = Graph.MeshData(graphA)
|
13307
|
+
mesh_data_b = Graph.MeshData(graphB)
|
13308
|
+
edges_a = mesh_data_a['edges']
|
13309
|
+
edges_b = mesh_data_b['edges']
|
13310
|
+
edges_a_dicts = mesh_data_a['edgeDictionaries']
|
13311
|
+
edges_b_dicts = mesh_data_b['edgeDictionaries']
|
13312
|
+
|
13313
|
+
union_vertices = []
|
13314
|
+
|
13315
|
+
def _add_vertex(v):
|
13316
|
+
for i, uv in enumerate(union_vertices):
|
13317
|
+
if Graph._vertex_is_same(v, uv, keys=vertexKeys):
|
13318
|
+
d_c = Topology.Dictionary(v) # Dictionaries of graphB are imposed.
|
13319
|
+
if useCentroid:
|
13320
|
+
c = Topology.Centroid(Cluster.ByTopologies([v, uv]))
|
13321
|
+
else:
|
13322
|
+
c = v
|
13323
|
+
c = Topology.SetDictionary(c, d_c)
|
13324
|
+
union_vertices[i] = c
|
13325
|
+
return i
|
13326
|
+
union_vertices.append(v)
|
13327
|
+
return len(union_vertices) - 1
|
13328
|
+
|
13329
|
+
# Map original vertices to indices in union list
|
13330
|
+
index_map_a = [_add_vertex(v) for v in vertices_a]
|
13331
|
+
index_map_b = [_add_vertex(v) for v in vertices_b]
|
13332
|
+
|
13333
|
+
union_edges = []
|
13334
|
+
|
13335
|
+
def _add_edge(i, j, dictionary):
|
13336
|
+
vi = union_vertices[i]
|
13337
|
+
vj = union_vertices[j]
|
13338
|
+
for k, e in enumerate(union_edges):
|
13339
|
+
svi = Edge.StartVertex(e)
|
13340
|
+
evi = Edge.EndVertex(e)
|
13341
|
+
if (Graph._vertex_is_same(svi, vi, keys=vertexKeys, tolerance=tolerance) and Graph._vertex_is_same(evi, vj, keys=vertexKeys, tolerance=tolerance)) or \
|
13342
|
+
(Graph._vertex_is_same(svi, vj, keys=vertexKeys, tolerance=tolerance) and Graph._vertex_is_same(evi, vi, keys=vertexKeys, tolerance=tolerance)):
|
13343
|
+
# Impose edge dictionary from graphB
|
13344
|
+
new_edge = Edge.ByVertices(vi, vj)
|
13345
|
+
new_edge = Topology.SetDictionary(new_edge, dictionary, silent=True)
|
13346
|
+
union_edges[k] = new_edge
|
13347
|
+
return
|
13348
|
+
# If not found, add new edge
|
13349
|
+
edge = Edge.ByVertices(vi, vj)
|
13350
|
+
edge = Topology.SetDictionary(edge, dictionary)
|
13351
|
+
union_edges.append(edge)
|
13352
|
+
|
13353
|
+
# Add edges from A
|
13354
|
+
for idx, e in enumerate(edges_a):
|
13355
|
+
i = index_map_a[e[0]]
|
13356
|
+
j = index_map_a[e[1]]
|
13357
|
+
if not i == j:
|
13358
|
+
_add_edge(i, j, Dictionary.ByPythonDictionary(edges_a_dicts[idx]))
|
13359
|
+
|
13360
|
+
# Add edges from B, merging duplicates
|
13361
|
+
for idx, e in enumerate(edges_b):
|
13362
|
+
i = index_map_b[e[0]]
|
13363
|
+
j = index_map_b[e[1]]
|
13364
|
+
if not i == j:
|
13365
|
+
_add_edge(i, j, Dictionary.ByPythonDictionary(edges_b_dicts[idx]))
|
13059
13366
|
|
13367
|
+
return Graph.ByVerticesEdges(union_vertices, union_edges)
|
13368
|
+
|
13060
13369
|
@staticmethod
|
13061
|
-
def
|
13370
|
+
def Imprint(graphA, graphB, vertexKeys, useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
|
13062
13371
|
"""
|
13063
|
-
|
13372
|
+
Imprints the second input graph on the first input graph based on the input vertex keys. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13064
13373
|
|
13065
13374
|
Parameters
|
13066
13375
|
----------
|
@@ -13068,8 +13377,15 @@ class Graph:
|
|
13068
13377
|
The first input graph.
|
13069
13378
|
graphB : topologic_core.Graph
|
13070
13379
|
The second input graph.
|
13071
|
-
|
13072
|
-
The vertex dictionary key to use to determine if two vertices are the same.
|
13380
|
+
vertexKeys : list or str , optional
|
13381
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13382
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13383
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13384
|
+
useCentroid : bool , optional
|
13385
|
+
If set to True, the coordinates of identical vertices from each graph are averaged to located the new merged vertex of the resulting graph.
|
13386
|
+
Otherwise, the coordinates of the vertex of the first input graph are used. The default is False.
|
13387
|
+
tolerance : float , optional
|
13388
|
+
The desired tolerance. The default is 0.0001.
|
13073
13389
|
silent : bool , optional
|
13074
13390
|
If set to True, error and warning messages are suppressed. The default is False.
|
13075
13391
|
|
@@ -13081,20 +13397,126 @@ class Graph:
|
|
13081
13397
|
"""
|
13082
13398
|
from topologicpy.Vertex import Vertex
|
13083
13399
|
from topologicpy.Edge import Edge
|
13400
|
+
from topologicpy.Cluster import Cluster
|
13084
13401
|
from topologicpy.Topology import Topology
|
13085
13402
|
from topologicpy.Dictionary import Dictionary
|
13086
13403
|
|
13087
13404
|
if not Topology.IsInstance(graphA, "graph"):
|
13088
13405
|
if not silent:
|
13089
|
-
print("Graph.
|
13406
|
+
print("Graph.Imprint - Error: The graphA input parameter is not a valid graph. Returning None.")
|
13090
13407
|
return None
|
13091
13408
|
if not Topology.IsInstance(graphB, "graph"):
|
13092
13409
|
if not silent:
|
13093
|
-
print("Graph.
|
13410
|
+
print("Graph.Imprint - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13411
|
+
return None
|
13412
|
+
|
13413
|
+
vertices_a = Graph.Vertices(graphA)
|
13414
|
+
vertices_a_new = []
|
13415
|
+
for v in vertices_a:
|
13416
|
+
d = Topology.Dictionary(v)
|
13417
|
+
v_new = Vertex.ByCoordinates(Vertex.Coordinates(v))
|
13418
|
+
v_new = Topology.SetDictionary(v_new, d)
|
13419
|
+
vertices_a_new.append(v_new)
|
13420
|
+
vertices_a = vertices_a_new
|
13421
|
+
vertices_b = Graph.Vertices(graphB)
|
13422
|
+
vertices_b_new = []
|
13423
|
+
for v in vertices_b:
|
13424
|
+
d = Topology.Dictionary(v)
|
13425
|
+
v_new = Vertex.ByCoordinates(Vertex.Coordinates(v))
|
13426
|
+
v_new = Topology.SetDictionary(v_new, d)
|
13427
|
+
vertices_b_new.append(v_new)
|
13428
|
+
vertices_b = vertices_b_new
|
13429
|
+
mesh_data_a = Graph.MeshData(graphA)
|
13430
|
+
mesh_data_b = Graph.MeshData(graphB)
|
13431
|
+
topo_edges_a = Graph.Edges(graphA)
|
13432
|
+
edges_a = mesh_data_a['edges']
|
13433
|
+
edges_b = mesh_data_b['edges']
|
13434
|
+
edges_b_dicts = mesh_data_b['edgeDictionaries']
|
13435
|
+
|
13436
|
+
final_vertices = []
|
13437
|
+
vertex_map = {}
|
13438
|
+
for i, a in enumerate(vertices_a):
|
13439
|
+
j = Graph._vertex_in_list(a, vertices_b, keys=vertexKeys, tolerance=tolerance)
|
13440
|
+
if j:
|
13441
|
+
b = vertices_b[j-1]
|
13442
|
+
if useCentroid:
|
13443
|
+
c = Topology.Centroid(Cluster.ByTopologies([a, b]))
|
13444
|
+
else:
|
13445
|
+
c = a
|
13446
|
+
d_c = Topology.Dictionary(b)
|
13447
|
+
c = Topology.SetDictionary(c, d_c, silent=True)
|
13448
|
+
vertex_map[i] = c
|
13449
|
+
final_vertices.append(c)
|
13450
|
+
else:
|
13451
|
+
final_vertices.append(a)
|
13452
|
+
if len(final_vertices) < 1:
|
13453
|
+
if not silent:
|
13454
|
+
print("Graph.Imprint - Warning: graphA and graphB do not intersect. Returning None.")
|
13455
|
+
return None
|
13456
|
+
|
13457
|
+
final_edges = []
|
13458
|
+
|
13459
|
+
for i, e in enumerate(edges_a):
|
13460
|
+
j = Graph._edge_in_list(e, edges_b, vertices_a, vertices_b, keys=vertexKeys, tolerance=tolerance)
|
13461
|
+
if j:
|
13462
|
+
# Merge the dictionaries
|
13463
|
+
d_c = Dictionary.ByPythonDictionary(edges_b_dicts[j-1]) # We added 1 to j to avoid 0 which can be interpreted as False.
|
13464
|
+
# Create the edge
|
13465
|
+
#final_edge = Edge.ByVertices(vertices_a[e[0]], vertices_a[e[1]])
|
13466
|
+
sv = vertex_map[e[0]]
|
13467
|
+
ev = vertex_map[e[1]]
|
13468
|
+
final_edge = Edge.ByVertices(sv, ev)
|
13469
|
+
# Set the edge's dictionary
|
13470
|
+
final_edge = Topology.SetDictionary(final_edge, d_c, silent=True)
|
13471
|
+
# Add the final edge to the list
|
13472
|
+
final_edges.append(final_edge)
|
13473
|
+
else:
|
13474
|
+
final_edges.append(topo_edges_a[i])
|
13475
|
+
|
13476
|
+
return Graph.ByVerticesEdges(final_vertices, final_edges)
|
13477
|
+
|
13478
|
+
@staticmethod
|
13479
|
+
def Intersect(graphA, graphB, vertexKeys, vertexColorKey="color", useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
|
13480
|
+
"""
|
13481
|
+
Intersect the two input graphs based on the input vertex keys. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13482
|
+
|
13483
|
+
Parameters
|
13484
|
+
----------
|
13485
|
+
graphA : topologic_core.Graph
|
13486
|
+
The first input graph.
|
13487
|
+
graphB : topologic_core.Graph
|
13488
|
+
The second input graph.
|
13489
|
+
vertexKeys : list or str , optional
|
13490
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13491
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13492
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13493
|
+
useCentroid : bool , optional
|
13494
|
+
If set to True, the coordinates of identical vertices from each graph are averaged to located the new merged vertex of the resulting graph.
|
13495
|
+
Otherwise, the coordinates of the vertex of the first input graph are used. The default is False.
|
13496
|
+
tolerance : float , optional
|
13497
|
+
The desired tolerance. The default is 0.0001.
|
13498
|
+
silent : bool , optional
|
13499
|
+
If set to True, error and warning messages are suppressed. The default is False.
|
13500
|
+
|
13501
|
+
Returns
|
13502
|
+
-------
|
13503
|
+
topologic_core.Graph
|
13504
|
+
the resultant graph. Vertex and edge dictionaries are merged.
|
13505
|
+
|
13506
|
+
"""
|
13507
|
+
from topologicpy.Vertex import Vertex
|
13508
|
+
from topologicpy.Edge import Edge
|
13509
|
+
from topologicpy.Cluster import Cluster
|
13510
|
+
from topologicpy.Topology import Topology
|
13511
|
+
from topologicpy.Dictionary import Dictionary
|
13512
|
+
|
13513
|
+
if not Topology.IsInstance(graphA, "graph"):
|
13514
|
+
if not silent:
|
13515
|
+
print("Graph.Intersect - Error: The graphA input parameter is not a valid graph. Returning None.")
|
13094
13516
|
return None
|
13095
|
-
if not
|
13517
|
+
if not Topology.IsInstance(graphB, "graph"):
|
13096
13518
|
if not silent:
|
13097
|
-
print("Graph.Intersect - Error: The
|
13519
|
+
print("Graph.Intersect - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13098
13520
|
return None
|
13099
13521
|
|
13100
13522
|
vertices_a = Graph.Vertices(graphA)
|
@@ -13121,27 +13543,40 @@ class Graph:
|
|
13121
13543
|
edges_b_dicts = mesh_data_b['edgeDictionaries']
|
13122
13544
|
|
13123
13545
|
common_vertices = []
|
13124
|
-
|
13125
|
-
|
13546
|
+
vertex_map = {}
|
13547
|
+
for i, a in enumerate(vertices_a):
|
13548
|
+
j = Graph._vertex_in_list(a, vertices_b, keys=vertexKeys, tolerance=tolerance)
|
13126
13549
|
if j:
|
13127
|
-
|
13128
|
-
|
13550
|
+
b = vertices_b[j-1]
|
13551
|
+
if useCentroid:
|
13552
|
+
c = Topology.Centroid(Cluster.ByTopologies([a, b]))
|
13553
|
+
else:
|
13554
|
+
c = a
|
13555
|
+
d_a = Topology.Dictionary(a)
|
13556
|
+
d_b = Topology.Dictionary(b)
|
13129
13557
|
d_c = Dictionary.ByMergedDictionaries([d_a, d_b], silent=True)
|
13130
|
-
|
13131
|
-
|
13558
|
+
c = Topology.SetDictionary(c, d_c, silent=True)
|
13559
|
+
vertex_map[i] = c
|
13560
|
+
common_vertices.append(c)
|
13561
|
+
if len(common_vertices) < 1:
|
13562
|
+
if not silent:
|
13563
|
+
print("Graph.Intersect - Warning: graphA and graphB do not intersect. Returning None.")
|
13564
|
+
return None
|
13565
|
+
|
13132
13566
|
common_edges = []
|
13133
13567
|
|
13134
13568
|
for i, e in enumerate(edges_a):
|
13135
|
-
j = Graph._edge_in_list(e, edges_b, vertices_a, vertices_b,
|
13569
|
+
j = Graph._edge_in_list(e, edges_b, vertices_a, vertices_b, keys=vertexKeys, tolerance=tolerance)
|
13136
13570
|
if j:
|
13137
13571
|
# Merge the dictionaries
|
13138
13572
|
d_a = Dictionary.ByPythonDictionary(edges_a_dicts[i])
|
13139
13573
|
d_b = Dictionary.ByPythonDictionary(edges_b_dicts[j-1]) # We added 1 to j to avoid 0 which can be interpreted as False.
|
13140
13574
|
d_c = Dictionary.ByMergedDictionaries([d_a, d_b], silent=True)
|
13141
|
-
print("Intersect - d_c:", d_c)
|
13142
|
-
print(Dictionary.Keys(d_c), Dictionary.Values(d_c))
|
13143
13575
|
# Create the edge
|
13144
|
-
final_edge = Edge.ByVertices(vertices_a[e[0]], vertices_a[e[1]])
|
13576
|
+
#final_edge = Edge.ByVertices(vertices_a[e[0]], vertices_a[e[1]])
|
13577
|
+
sv = vertex_map[e[0]]
|
13578
|
+
ev = vertex_map[e[1]]
|
13579
|
+
final_edge = Edge.ByVertices(sv, ev)
|
13145
13580
|
# Set the edge's dictionary
|
13146
13581
|
final_edge = Topology.SetDictionary(final_edge, d_c, silent=True)
|
13147
13582
|
# Add the final edge to the list
|
@@ -13150,9 +13585,9 @@ class Graph:
|
|
13150
13585
|
return Graph.ByVerticesEdges(common_vertices, common_edges)
|
13151
13586
|
|
13152
13587
|
@staticmethod
|
13153
|
-
def Difference(graphA, graphB,
|
13588
|
+
def Difference(graphA, graphB, vertexKeys, useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
|
13154
13589
|
"""
|
13155
|
-
Intersect the two input graphs based on
|
13590
|
+
Intersect the two input graphs based on the input vertex keys. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13156
13591
|
|
13157
13592
|
Parameters
|
13158
13593
|
----------
|
@@ -13160,8 +13595,14 @@ class Graph:
|
|
13160
13595
|
The first input graph.
|
13161
13596
|
graphB : topologic_core.Graph
|
13162
13597
|
The second input graph.
|
13163
|
-
|
13164
|
-
The vertex dictionary key to use to determine if two vertices are the same.
|
13598
|
+
vertexKeys : list or str , optional
|
13599
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13600
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13601
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13602
|
+
useCentroid : bool , optional
|
13603
|
+
This is not used here, but included for API consistency for boolean operations.
|
13604
|
+
tolerance : float , optional
|
13605
|
+
The desired tolerance. The default is 0.0001.
|
13165
13606
|
silent : bool , optional
|
13166
13607
|
If set to True, error and warning messages are suppressed. The default is False.
|
13167
13608
|
|
@@ -13184,25 +13625,36 @@ class Graph:
|
|
13184
13625
|
if not silent:
|
13185
13626
|
print("Graph.Difference - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13186
13627
|
return None
|
13187
|
-
|
13188
|
-
if not silent:
|
13189
|
-
print("Graph.Difference - Error: The vertexKey input parameter is not a valid string. Returning None.")
|
13190
|
-
return None
|
13628
|
+
|
13191
13629
|
vertices_a = Graph.Vertices(graphA)
|
13630
|
+
vertices_a_new = []
|
13631
|
+
for v in vertices_a:
|
13632
|
+
d = Topology.Dictionary(v)
|
13633
|
+
v_new = Vertex.ByCoordinates(Vertex.Coordinates(v))
|
13634
|
+
v_new = Topology.SetDictionary(v_new, d)
|
13635
|
+
vertices_a_new.append(v_new)
|
13636
|
+
vertices_a = vertices_a_new
|
13192
13637
|
vertices_b = Graph.Vertices(graphB)
|
13638
|
+
vertices_b_new = []
|
13639
|
+
for v in vertices_b:
|
13640
|
+
d = Topology.Dictionary(v)
|
13641
|
+
v_new = Vertex.ByCoordinates(Vertex.Coordinates(v))
|
13642
|
+
v_new = Topology.SetDictionary(v_new, d)
|
13643
|
+
vertices_b_new.append(v_new)
|
13644
|
+
vertices_b = vertices_b_new
|
13193
13645
|
mesh_data_a = Graph.MeshData(graphA)
|
13194
13646
|
mesh_data_b = Graph.MeshData(graphB)
|
13195
13647
|
edges_a = mesh_data_a['edges']
|
13196
13648
|
edges_b = mesh_data_b['edges']
|
13197
13649
|
edges_a_dicts = mesh_data_a['edgeDictionaries']
|
13198
13650
|
|
13199
|
-
diff_vertices = [v for v in vertices_a if not Graph._vertex_in_list(v, vertices_b,
|
13651
|
+
diff_vertices = [v for v in vertices_a if not Graph._vertex_in_list(v, vertices_b, keys=vertexKeys, tolerance=tolerance)]
|
13200
13652
|
diff_edges = []
|
13201
13653
|
|
13202
13654
|
for i, e in enumerate(edges_a):
|
13203
|
-
if not Graph._edge_in_list(e, edges_b, vertices_a, vertices_b,
|
13655
|
+
if not Graph._edge_in_list(e, edges_b, vertices_a, vertices_b, keys=vertexKeys, tolerance=tolerance):
|
13204
13656
|
# Create the edge
|
13205
|
-
if Graph._vertex_in_list(vertices_a[e[0]], diff_vertices,
|
13657
|
+
if Graph._vertex_in_list(vertices_a[e[0]], diff_vertices, keys=vertexKeys, tolerance=tolerance) and Graph._vertex_in_list(vertices_a[e[1]], diff_vertices, keys=vertexKeys, tolerance=tolerance):
|
13206
13658
|
final_edge = Edge.ByVertices(vertices_a[e[0]], vertices_a[e[1]])
|
13207
13659
|
# Set the edge's dictionary
|
13208
13660
|
final_edge = Topology.SetDictionary(final_edge, Dictionary.ByPythonDictionary(edges_a_dicts[i]), silent=True)
|
@@ -13212,9 +13664,9 @@ class Graph:
|
|
13212
13664
|
return Graph.ByVerticesEdges(diff_vertices, diff_edges)
|
13213
13665
|
|
13214
13666
|
@staticmethod
|
13215
|
-
def
|
13667
|
+
def Merge(graphA, graphB, vertexKeys=None, vertexColorKey="color", useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
|
13216
13668
|
"""
|
13217
|
-
|
13669
|
+
Merges the two input graphs based on the input vertex keys. This is an alias for Graph.Union. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13218
13670
|
|
13219
13671
|
Parameters
|
13220
13672
|
----------
|
@@ -13222,8 +13674,57 @@ class Graph:
|
|
13222
13674
|
The first input graph.
|
13223
13675
|
graphB : topologic_core.Graph
|
13224
13676
|
The second input graph.
|
13225
|
-
|
13226
|
-
The vertex dictionary key to use to determine if two vertices are the same.
|
13677
|
+
vertexKeys : list or str , optional
|
13678
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13679
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13680
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13681
|
+
vertexColorKey : str , optional
|
13682
|
+
The dictionary key that is storing the vertex's color. The final colors will be averaged. The default is "color".
|
13683
|
+
useCentroid : bool , optional
|
13684
|
+
If set to True, the coordinates of identical vertices from each graph are averaged to located the new merged vertex of the resulting graph.
|
13685
|
+
Otherwise, the coordinates of the vertex of the first input graph are used. The default is False.
|
13686
|
+
tolerance : float , optional
|
13687
|
+
The desired tolerance. The default is 0.0001.
|
13688
|
+
silent : bool , optional
|
13689
|
+
If set to True, error and warning messages are suppressed. The default is False.
|
13690
|
+
|
13691
|
+
Returns
|
13692
|
+
-------
|
13693
|
+
topologic_core.Graph
|
13694
|
+
the resultant graph. Vertex and edge dictionaries are merged.
|
13695
|
+
|
13696
|
+
"""
|
13697
|
+
from topologicpy.Topology import Topology
|
13698
|
+
|
13699
|
+
if not Topology.IsInstance(graphA, "graph"):
|
13700
|
+
if not silent:
|
13701
|
+
print("Graph.Union - Error: The graphA input parameter is not a valid graph. Returning None.")
|
13702
|
+
return None
|
13703
|
+
if not Topology.IsInstance(graphB, "graph"):
|
13704
|
+
if not silent:
|
13705
|
+
print("Graph.Union - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13706
|
+
return None
|
13707
|
+
return Graph.Union(graphA, graphB, vertexKeys=vertexKeys, vertexColorKey=vertexColorKey, useCentroid=useCentroid, tolerance=tolerance, silent=silent)
|
13708
|
+
|
13709
|
+
@staticmethod
|
13710
|
+
def SymmetricDifference(graphA, graphB, vertexKeys, useCentroid: bool = False, tolerance: float = 0.001, silent: bool = False):
|
13711
|
+
"""
|
13712
|
+
Find the symmetric difference (exclusive OR / XOR) of the two input graphs based on the input vertex keys. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13713
|
+
|
13714
|
+
Parameters
|
13715
|
+
----------
|
13716
|
+
graphA : topologic_core.Graph
|
13717
|
+
The first input graph.
|
13718
|
+
graphB : topologic_core.Graph
|
13719
|
+
The second input graph.
|
13720
|
+
vertexKeys : list or str , optional
|
13721
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13722
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13723
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13724
|
+
useCentroid : bool , optional
|
13725
|
+
This is not used here, but included for API consistency for boolean operations.
|
13726
|
+
tolerance : float , optional
|
13727
|
+
The desired tolerance. The default is 0.0001.
|
13227
13728
|
silent : bool , optional
|
13228
13729
|
If set to True, error and warning messages are suppressed. The default is False.
|
13229
13730
|
|
@@ -13244,10 +13745,49 @@ class Graph:
|
|
13244
13745
|
if not silent:
|
13245
13746
|
print("Graph.SymmetricDifference - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13246
13747
|
return None
|
13247
|
-
|
13748
|
+
diffAB = Graph.Difference(graphA, graphB, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13749
|
+
diffBA = Graph.Difference(graphB, graphA, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13750
|
+
return Graph.Union(diffAB, diffBA, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13751
|
+
|
13752
|
+
@staticmethod
|
13753
|
+
def XOR(graphA, graphB, vertexKeys, useCentroid: bool = False, tolerance: float = 0.001, silent: bool = False):
|
13754
|
+
"""
|
13755
|
+
Find the symmetric difference (exclusive OR / XOR) of the two input graphs based on an input vertex key. See https://en.wikipedia.org/wiki/Boolean_operation.
|
13756
|
+
|
13757
|
+
Parameters
|
13758
|
+
----------
|
13759
|
+
graphA : topologic_core.Graph
|
13760
|
+
The first input graph.
|
13761
|
+
graphB : topologic_core.Graph
|
13762
|
+
The second input graph.
|
13763
|
+
vertexKeys : list or str , optional
|
13764
|
+
The vertex dictionary key (str) or keys (list of str) to use to determine if two vertices are the same.
|
13765
|
+
If the vertexKeys are set to None or "loc" or "coord" or "xyz" (case insensitive), the distance between the
|
13766
|
+
vertices (within the tolerance) will be used to determine sameness. The default is None.
|
13767
|
+
useCentroid : bool , optional
|
13768
|
+
This is not used here, but included for API consistency for boolean operations.
|
13769
|
+
tolerance : float , optional
|
13770
|
+
The desired tolerance. The default is 0.0001.
|
13771
|
+
silent : bool , optional
|
13772
|
+
If set to True, error and warning messages are suppressed. The default is False.
|
13773
|
+
|
13774
|
+
Returns
|
13775
|
+
-------
|
13776
|
+
topologic_core.Graph
|
13777
|
+
the resultant graph. Vertex and edge dictionaries are not merged.
|
13778
|
+
|
13779
|
+
"""
|
13780
|
+
|
13781
|
+
from topologicpy.Topology import Topology
|
13782
|
+
|
13783
|
+
if not Topology.IsInstance(graphA, "graph"):
|
13784
|
+
if not silent:
|
13785
|
+
print("Graph.XOR - Error: The graphA input parameter is not a valid graph. Returning None.")
|
13786
|
+
return None
|
13787
|
+
if not Topology.IsInstance(graphB, "graph"):
|
13248
13788
|
if not silent:
|
13249
|
-
print("Graph.
|
13789
|
+
print("Graph.XOR - Error: The graphB input parameter is not a valid graph. Returning None.")
|
13250
13790
|
return None
|
13251
|
-
diffAB = Graph.Difference(graphA, graphB,
|
13252
|
-
diffBA = Graph.Difference(graphB, graphA,
|
13253
|
-
return Graph.Union(diffAB, diffBA,
|
13791
|
+
diffAB = Graph.Difference(graphA, graphB, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13792
|
+
diffBA = Graph.Difference(graphB, graphA, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13793
|
+
return Graph.Union(diffAB, diffBA, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
topologicpy/Plotly.py
CHANGED
@@ -277,6 +277,9 @@ class Plotly:
|
|
277
277
|
absolute: bool = False,
|
278
278
|
sides: int = 8,
|
279
279
|
angle: float = 0,
|
280
|
+
directed: bool = False,
|
281
|
+
arrowSize: int = 0.1,
|
282
|
+
arrowSizeKey: str = None,
|
280
283
|
vertexColor: str = "black",
|
281
284
|
vertexColorKey: str = None,
|
282
285
|
vertexSize: float = 10,
|
@@ -330,6 +333,12 @@ class Plotly:
|
|
330
333
|
For example, if the length of the edge is 10, the sagitta is set to 0.5, and absolute is set to False, the sagitta length will be 5. The default is True.
|
331
334
|
sides : int , optional
|
332
335
|
The number of sides of the arc. The default is 8.
|
336
|
+
directed : bool , optional
|
337
|
+
If set to True, arrowheads are drawn to show direction. The default is False.
|
338
|
+
arrowSize : int, optional
|
339
|
+
The desired size of arrowheads for directed graphs. The default is 0.1.
|
340
|
+
arrowSizeKey: str , optional
|
341
|
+
The edge dictionary key under which to find the arrowhead size. The default is None.
|
333
342
|
vertexColor : str , optional
|
334
343
|
The desired color of the output vertices. This can be any plotly color string and may be specified as:
|
335
344
|
- A hex string (e.g. '#ff0000')
|
@@ -479,7 +488,7 @@ class Plotly:
|
|
479
488
|
geo = Topology.Geometry(e_cluster, mantissa=mantissa)
|
480
489
|
vertices = geo['vertices']
|
481
490
|
edges = geo['edges']
|
482
|
-
data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, dash=edgeDash, dashKey=edgeDashKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
|
491
|
+
data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, dash=edgeDash, dashKey=edgeDashKey, directed=directed, arrowSize=arrowSize, arrowSizeKey=arrowSizeKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
|
483
492
|
return data
|
484
493
|
|
485
494
|
@staticmethod
|
@@ -638,11 +647,60 @@ class Plotly:
|
|
638
647
|
return return_value
|
639
648
|
|
640
649
|
@staticmethod
|
641
|
-
def edgeData(vertices, edges, dictionaries=None, color="black", colorKey=None, width=1, widthKey=None, dash=False, dashKey=None, labelKey=None, showEdgeLabel = False, groupKey=None, minGroup=None, maxGroup=None, groups=[], legendLabel="Topology Edges", legendGroup=2, legendRank=2, showLegend=True, colorScale="Viridis"):
|
650
|
+
def edgeData(vertices, edges, dictionaries=None, color="black", colorKey=None, width=1, widthKey=None, dash=False, dashKey=None, directed=False, arrowSize=0.1, arrowSizeKey=None, labelKey=None, showEdgeLabel = False, groupKey=None, minGroup=None, maxGroup=None, groups=[], legendLabel="Topology Edges", legendGroup=2, legendRank=2, showLegend=True, colorScale="Viridis"):
|
642
651
|
|
643
652
|
from topologicpy.Color import Color
|
644
653
|
from topologicpy.Dictionary import Dictionary
|
645
654
|
from topologicpy.Helper import Helper
|
655
|
+
|
656
|
+
def create_arrowheads(x, y, z, arrowSize=4):
|
657
|
+
import plotly.graph_objects as go
|
658
|
+
import numpy as np
|
659
|
+
|
660
|
+
arrow_traces = []
|
661
|
+
|
662
|
+
# Compute segment directions
|
663
|
+
cone_x, cone_y, cone_z = [], [], []
|
664
|
+
cone_u, cone_v, cone_w = [], [], []
|
665
|
+
|
666
|
+
for i in range(len(x) - 1):
|
667
|
+
if x[i] == None or x[i+1] == None:
|
668
|
+
continue
|
669
|
+
u = x[i+1] - x[i]
|
670
|
+
v = y[i+1] - y[i]
|
671
|
+
w = z[i+1] - z[i]
|
672
|
+
|
673
|
+
norm = np.linalg.norm([u, v, w])
|
674
|
+
if norm > 0:
|
675
|
+
u /= norm
|
676
|
+
v /= norm
|
677
|
+
w /= norm
|
678
|
+
cone_x.append(x[i+1])
|
679
|
+
cone_y.append(y[i+1])
|
680
|
+
cone_z.append(z[i+1])
|
681
|
+
cone_u.append(u)
|
682
|
+
cone_v.append(v)
|
683
|
+
cone_w.append(w)
|
684
|
+
|
685
|
+
cone_trace = go.Cone(
|
686
|
+
x=cone_x,
|
687
|
+
y=cone_y,
|
688
|
+
z=cone_z,
|
689
|
+
u=cone_u,
|
690
|
+
v=cone_v,
|
691
|
+
w=cone_w,
|
692
|
+
sizemode="raw",
|
693
|
+
sizeref=arrowSize,
|
694
|
+
showscale=False,
|
695
|
+
colorscale=[[0, color], [1, color]],
|
696
|
+
anchor="tip",
|
697
|
+
name="", # Hide legend
|
698
|
+
hoverinfo="skip",
|
699
|
+
showlegend=False
|
700
|
+
)
|
701
|
+
arrow_traces.append(cone_trace)
|
702
|
+
|
703
|
+
return arrow_traces
|
646
704
|
traces = []
|
647
705
|
x = []
|
648
706
|
y = []
|
@@ -676,8 +734,8 @@ class Plotly:
|
|
676
734
|
maxGroup = 1
|
677
735
|
|
678
736
|
|
679
|
-
if colorKey or widthKey or labelKey or groupKey or dashKey:
|
680
|
-
keys = [x for x in [colorKey, widthKey, labelKey, groupKey, dashKey] if not x == None]
|
737
|
+
if colorKey or widthKey or labelKey or groupKey or dashKey or arrowSizeKey:
|
738
|
+
keys = [x for x in [colorKey, widthKey, labelKey, groupKey, dashKey, arrowSizeKey] if not x == None]
|
681
739
|
temp_dict = Helper.ClusterByKeys(edges, dictionaries, keys, silent=False)
|
682
740
|
dict_clusters = temp_dict["dictionaries"]
|
683
741
|
elements_clusters = temp_dict['elements']
|
@@ -692,6 +750,8 @@ class Plotly:
|
|
692
750
|
d_color = Color.AnyToHex(d_color)
|
693
751
|
if not dashKey == None:
|
694
752
|
d_dash = Dictionary.ValueAtKey(d, key=dashKey) or dash
|
753
|
+
if not arrowSizeKey == None:
|
754
|
+
d_arrowSize = Dictionary.ValueAtKey(d, key=arrowSizeKey) or arrowSize
|
695
755
|
if not labelKey == None:
|
696
756
|
labels.append(str(Dictionary.ValueAtKey(d, labelKey, "")))
|
697
757
|
if not widthKey == None:
|
@@ -744,6 +804,9 @@ class Plotly:
|
|
744
804
|
text=labels,
|
745
805
|
hoverinfo='text',
|
746
806
|
hovertext=labels)
|
807
|
+
if directed:
|
808
|
+
arrow_traces = create_arrowheads(x, y, z, arrowSize=d_arrowSize)
|
809
|
+
traces += arrow_traces
|
747
810
|
traces.append(trace)
|
748
811
|
else:
|
749
812
|
x = []
|
@@ -775,6 +838,9 @@ class Plotly:
|
|
775
838
|
legendrank=legendRank,
|
776
839
|
text=label,
|
777
840
|
hoverinfo='text')
|
841
|
+
if directed:
|
842
|
+
arrow_traces = create_arrowheads(x, y, z, arrowSize=arrowSize)
|
843
|
+
traces += arrow_traces
|
778
844
|
traces.append(trace)
|
779
845
|
return traces
|
780
846
|
|
@@ -799,6 +865,9 @@ class Plotly:
|
|
799
865
|
vertexLegendLabel="Topology Vertices",
|
800
866
|
vertexLegendRank=1,
|
801
867
|
vertexLegendGroup=1,
|
868
|
+
directed=False,
|
869
|
+
arrowSize=0.1,
|
870
|
+
arrowSizeKey=None,
|
802
871
|
showEdges=True,
|
803
872
|
edgeWidth=1,
|
804
873
|
edgeWidthKey=None,
|
@@ -884,7 +953,12 @@ class Plotly:
|
|
884
953
|
The legend rank order of the vertices of this topology. The default is 1.
|
885
954
|
vertexLegendGroup : int , optional
|
886
955
|
The number of the vertex legend group to which the vertices of this topology belong. The default is 1.
|
887
|
-
|
956
|
+
directed : bool , optional
|
957
|
+
If set to True, arrowheads are drawn to show direction. The default is False.
|
958
|
+
arrowSize : int, optional
|
959
|
+
The desired size of arrowheads for directed graphs. The default is 0.1.
|
960
|
+
arrowSizeKey: str , optional
|
961
|
+
The edge dictionary key under which to find the arrowhead size. The default is None.
|
888
962
|
showEdges : bool , optional
|
889
963
|
If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
|
890
964
|
edgeWidth : float , optional
|
@@ -1194,7 +1268,7 @@ class Plotly:
|
|
1194
1268
|
vertices = geo['vertices']
|
1195
1269
|
edges = geo['edges']
|
1196
1270
|
if len(edges) > 0:
|
1197
|
-
data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
|
1271
|
+
data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, directed=directed, arrowSize=arrowSize, arrowSizeKey=arrowSizeKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
|
1198
1272
|
|
1199
1273
|
if showFaces and Topology.Type(topology) >= Topology.TypeID("Face"):
|
1200
1274
|
if not faceColorKey == None:
|
topologicpy/Topology.py
CHANGED
@@ -8162,7 +8162,10 @@ class Topology():
|
|
8162
8162
|
vertexMaxGroup=None,
|
8163
8163
|
showVertexLegend=False,
|
8164
8164
|
vertexLegendLabel="Vertices",
|
8165
|
-
|
8165
|
+
|
8166
|
+
directed=False,
|
8167
|
+
arrowSize=0.1,
|
8168
|
+
arrowSizeKey=None,
|
8166
8169
|
showEdges=True,
|
8167
8170
|
edgeWidth=None,
|
8168
8171
|
edgeWidthKey=None,
|
@@ -8281,7 +8284,12 @@ class Topology():
|
|
8281
8284
|
If set to True, the legend for the vertices of this topology is shown. Otherwise, it isn't. The default is False.
|
8282
8285
|
vertexLegendLabel : str , optional
|
8283
8286
|
The legend label string used to identify vertices. The default is "Topology Vertices".
|
8284
|
-
|
8287
|
+
directed : bool , optional
|
8288
|
+
If set to True, arrowheads are drawn to show direction. The default is False.
|
8289
|
+
arrowSize : int, optional
|
8290
|
+
The desired size of arrowheads for directed graphs. The default is 0.1.
|
8291
|
+
arrowSizeKey: str , optional
|
8292
|
+
The edge dictionary key under which to find the arrowhead size. The default is None.
|
8285
8293
|
showEdges : bool , optional
|
8286
8294
|
If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
|
8287
8295
|
edgeWidth : float , optional
|
@@ -8470,6 +8478,9 @@ class Topology():
|
|
8470
8478
|
absolute=absolute,
|
8471
8479
|
sides=sides,
|
8472
8480
|
angle=angle,
|
8481
|
+
directed=directed,
|
8482
|
+
arrowSize=arrowSize,
|
8483
|
+
arrowSizeKey=arrowSizeKey,
|
8473
8484
|
vertexColor=vertexColor,
|
8474
8485
|
vertexColorKey=vertexColorKey,
|
8475
8486
|
vertexSize=vSize,
|
@@ -8543,6 +8554,9 @@ class Topology():
|
|
8543
8554
|
vertexLegendLabel=str(i+1)+": "+name+" ("+vertexLegendLabel+")",
|
8544
8555
|
vertexLegendRank=topology_counter+1,
|
8545
8556
|
vertexLegendGroup=topology_counter+1,
|
8557
|
+
directed=directed,
|
8558
|
+
arrowSize=arrowSize,
|
8559
|
+
arrowSizeKey=arrowSizeKey,
|
8546
8560
|
showEdges=showEdges,
|
8547
8561
|
edgeWidth=eWidth,
|
8548
8562
|
edgeWidthKey=edgeWidthKey,
|
topologicpy/Vertex.py
CHANGED
@@ -1819,7 +1819,7 @@ class Vertex():
|
|
1819
1819
|
return Vertex.ByCoordinates(pt[0], pt[1], pt[2])
|
1820
1820
|
|
1821
1821
|
@staticmethod
|
1822
|
-
def Separate(*vertices, minDistance: float = 0.0001, silent: bool = False):
|
1822
|
+
def Separate(*vertices, minDistance: float = 0.0001, iterations: int = 100, strength: float = 0.1, tolerance: float = 0.0001, silent: bool = False):
|
1823
1823
|
"""
|
1824
1824
|
Separates the input vertices such that no two vertices are within the input minimum distance.
|
1825
1825
|
|
@@ -1829,6 +1829,12 @@ class Vertex():
|
|
1829
1829
|
One or more instances of a topologic vertex to be processed.
|
1830
1830
|
minDistance : float , optional
|
1831
1831
|
The desired minimum distance. The default is 0.0001.
|
1832
|
+
iterations : int
|
1833
|
+
The number of iterations to run the repulsion simulation. The default is 100.
|
1834
|
+
strength : float
|
1835
|
+
The force multiplier controlling how strongly vertices repel each other. The default is 0.1.
|
1836
|
+
tolerance : float
|
1837
|
+
The desired tolerance. The default is 0.0001.
|
1832
1838
|
silent : bool , optional
|
1833
1839
|
If set to True, error and warning messages are suppressed. The default is False.
|
1834
1840
|
|
@@ -1840,10 +1846,7 @@ class Vertex():
|
|
1840
1846
|
"""
|
1841
1847
|
from topologicpy.Topology import Topology
|
1842
1848
|
from topologicpy.Helper import Helper
|
1843
|
-
|
1844
|
-
from math import sqrt
|
1845
|
-
from scipy.spatial import KDTree
|
1846
|
-
import numpy as np
|
1849
|
+
import math
|
1847
1850
|
|
1848
1851
|
if len(vertices) == 0:
|
1849
1852
|
if not silent:
|
@@ -1873,33 +1876,37 @@ class Vertex():
|
|
1873
1876
|
if not silent:
|
1874
1877
|
print("Vertex.Separate - Error: The input parameters do not contain any valid vertices. Returning None.")
|
1875
1878
|
return None
|
1876
|
-
|
1877
|
-
|
1878
|
-
|
1879
|
+
|
1880
|
+
minDistance = minDistance + tolerance # Add a bit of a safety factor
|
1881
|
+
|
1882
|
+
# Convert to mutable coordinates
|
1883
|
+
coords = [[v.X(), v.Y(), v.Z()] for v in vertices]
|
1884
|
+
|
1885
|
+
for _ in range(iterations):
|
1886
|
+
for i in range(len(coords)):
|
1887
|
+
for j in range(i + 1, len(coords)):
|
1888
|
+
dx = coords[j][0] - coords[i][0]
|
1889
|
+
dy = coords[j][1] - coords[i][1]
|
1890
|
+
dz = coords[j][2] - coords[i][2]
|
1891
|
+
dist = math.sqrt(dx*dx + dy*dy + dz*dz)
|
1892
|
+
|
1893
|
+
if dist < minDistance and dist > 1e-9:
|
1894
|
+
# Calculate repulsion vector
|
1895
|
+
repel = (minDistance - dist) / dist * strength
|
1896
|
+
shift = [dx * repel * 0.5, dy * repel * 0.5, dz * repel * 0.5]
|
1897
|
+
coords[i][0] -= shift[0]
|
1898
|
+
coords[i][1] -= shift[1]
|
1899
|
+
coords[i][2] -= shift[2]
|
1900
|
+
coords[j][0] += shift[0]
|
1901
|
+
coords[j][1] += shift[1]
|
1902
|
+
coords[j][2] += shift[2]
|
1879
1903
|
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
if distance < minDistance:
|
1887
|
-
# Move current vertex away from its neighbor
|
1888
|
-
adjustment = (minDistance - distance) / 2
|
1889
|
-
unit_vector = direction / distance if distance != 0 else np.random.rand(3)
|
1890
|
-
coords[i] -= unit_vector * adjustment
|
1891
|
-
coords[neighbor_index] += unit_vector * adjustment
|
1892
|
-
|
1893
|
-
# Rebuild the k-d tree after adjustment
|
1894
|
-
tree = KDTree(coords)
|
1895
|
-
|
1896
|
-
# Convert adjusted coordinates back to Vertex objects
|
1897
|
-
separated_vertices = [Vertex.ByCoordinates(x, y, z) for x, y, z in coords]
|
1898
|
-
for i, vertex in enumerate(vertexList):
|
1899
|
-
d = Topology.Dictionary(vertex)
|
1900
|
-
if len(Dictionary.Keys(d)) > 0:
|
1901
|
-
separated_vertices[i] = Topology.SetDictionary(separated_vertices[i], d)
|
1902
|
-
return separated_vertices
|
1904
|
+
# Reconstruct TopologicPy Vertex objects
|
1905
|
+
new_vertices = [Vertex.ByCoordinates(x, y, z) for x, y, z in coords]
|
1906
|
+
# Transfer the dictionaries
|
1907
|
+
for i, v in enumerate(new_vertices):
|
1908
|
+
v = Topology.SetDictionary(v, Topology.Dictionary(vertices[i]))
|
1909
|
+
return new_vertices
|
1903
1910
|
|
1904
1911
|
@staticmethod
|
1905
1912
|
def Transform(vertex, matrix, mantissa: int = 6, silent: bool = False):
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.8.
|
1
|
+
__version__ = '0.8.41'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.41
|
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
|
@@ -5,34 +5,34 @@ topologicpy/CSG.py,sha256=uDkOSmc8m1V_k7T3UCerODhOSyYNO4FRDzoOqt0kEt8,15590
|
|
5
5
|
topologicpy/Cell.py,sha256=3et98bv27m1-7OqFCeD4cLMBgC_BAsSGyxHi_4PgJ4Y,176072
|
6
6
|
topologicpy/CellComplex.py,sha256=NP6yptbkGXYmlfBv9fRimOnNle0mkjV8Yo_ug5NKpKE,60877
|
7
7
|
topologicpy/Cluster.py,sha256=wvfMAx6aPrSAt5nQ4--KnqD4EK9MGjch6Dg985WF7JQ,58748
|
8
|
-
topologicpy/Color.py,sha256=
|
8
|
+
topologicpy/Color.py,sha256=FcR0-__giyGQqvgiOrG8GkA65arHbiS33Si-QbUADPI,23362
|
9
9
|
topologicpy/Context.py,sha256=G3CwMvN8Jw2rnQRwB-n4MaQq_wLS0vPimbXKwsdMJ80,3055
|
10
10
|
topologicpy/DGL.py,sha256=HQXy9iDnrvWGDxaBfe5pRbweQ2zLBvAf6UdjfhKkQYI,139041
|
11
11
|
topologicpy/Dictionary.py,sha256=2Sxm8twR1W4ksZho0YXQB_EltK2qbZWK4UHskP3jvFQ,40846
|
12
12
|
topologicpy/Edge.py,sha256=CPdQKaE7ft6zgh0vxekkfGRRUY_yEqkEJ14NvjSgJOA,73190
|
13
13
|
topologicpy/EnergyModel.py,sha256=Pyb28gDDwhzlQIH0xqAygqS0P3SJxWyyV7OWS_AAfRs,53856
|
14
14
|
topologicpy/Face.py,sha256=BT_5ymb7-s-Wb1tuaBtkopJpeNg-RbooTUk_-KInQ6c,201618
|
15
|
-
topologicpy/Graph.py,sha256=
|
15
|
+
topologicpy/Graph.py,sha256=GykBbB8KKcy2oPV2Dsnz_vkyWGgczxLi0puVx5NJUYU,648088
|
16
16
|
topologicpy/Grid.py,sha256=EbI2NcYhQDpD5mItd7A1Lpr8Puuf87vZPWuoh7_gChQ,18483
|
17
17
|
topologicpy/Helper.py,sha256=qEsE4yaboEGW94q9lFCff0I_JwwTTQnDAFXw006yHaQ,31203
|
18
18
|
topologicpy/Honeybee.py,sha256=yctkwfdupKnp7bAOjP1Z4YaYpRrWoMEb4gz9Z5zaWwE,21751
|
19
19
|
topologicpy/Matrix.py,sha256=LqVckk2qTwKwEo79eyNsOrHVSHdO82JCREcfy6WIk4I,22716
|
20
20
|
topologicpy/Neo4j.py,sha256=ELKmON7R16j1kQD8xRHDGGCvzjIM2HGHNekdaXDUw6w,22371
|
21
|
-
topologicpy/Plotly.py,sha256=
|
21
|
+
topologicpy/Plotly.py,sha256=3jTy-bFNS_zFsfqpY_WdhHlt9kdexDq_Jvp5jxR16tQ,123710
|
22
22
|
topologicpy/Polyskel.py,sha256=oVfM4lqSMPTjnkHfsRU9VI8Blt6Vf0LVPkD9ebz7Wmw,27082
|
23
23
|
topologicpy/PyG.py,sha256=zvV6jtnol_aFiN6JRoMpYwBVfOU2aFs9gdWSdEo6mtU,109757
|
24
24
|
topologicpy/ShapeGrammar.py,sha256=UVb8VPwVKd6V3zDTNzpBecQPgYo1EjSsS10XJ8k5YcI,23364
|
25
25
|
topologicpy/Shell.py,sha256=fx0WTndC8blkvWe38nKsJsI_AmklOA0qsjU0gbZp4b4,90501
|
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=hGjePUb2-1MmyMva9nNtPiWr4CAWMa__BGX17mIOHfA,467519
|
29
29
|
topologicpy/Vector.py,sha256=X12eqskn28bdB7sLY1EZhq3noPYzPbNEgHPb4a959ss,42302
|
30
|
-
topologicpy/Vertex.py,sha256=
|
30
|
+
topologicpy/Vertex.py,sha256=RlGQnxQSb_kAus3tJgXd-v-Ptubtt09PQPA9IMwfXmI,84835
|
31
31
|
topologicpy/Wire.py,sha256=vE6IoObVucOZVTFMPiHuNN4DDezRHHyFbwhF5WRBm3s,231547
|
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=tObOvnnCfxEdJFf4I6CjFR-B5fIFs3s0EygfFnJZgQM,23
|
34
|
+
topologicpy-0.8.41.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
35
|
+
topologicpy-0.8.41.dist-info/METADATA,sha256=Fqkthwxq-nQkwkhyhz3ZvFA9C4tU6vPVwo2i3Q9QVmU,10535
|
36
|
+
topologicpy-0.8.41.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
37
|
+
topologicpy-0.8.41.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
38
|
+
topologicpy-0.8.41.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|