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 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, key=None):
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, key, "0")
12923
- b = Dictionary.ValueAtKey(d2, key, "1")
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, key=None):
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, key=key):
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, key=None):
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, key=key) and Graph._vertex_is_same(ev1, ev2, key=key)) or \
12941
- (Graph._vertex_is_same(sv1, ev2, key=key) and Graph._vertex_is_same(ev1, sv2, key=key)):
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, vertexKey: str, silent: bool = False):
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 an input vertex key. See https://en.wikipedia.org/wiki/Boolean_operation.
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
- vertexKey : str
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, key=vertexKey):
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
- uv = Topology.SetDictionary(uv, d_c)
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, key=vertexKey) and Graph._vertex_is_same(evi, vj, key=vertexKey)) or \
13033
- (Graph._vertex_is_same(svi, vj, key=vertexKey) and Graph._vertex_is_same(evi, vi, key=vertexKey)):
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
- _add_edge(i, j, Dictionary.ByPythonDictionary(edges_a_dicts[idx]))
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
- _add_edge(i, j, Dictionary.ByPythonDictionary(edges_b_dicts[idx]))
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 Intersect(graphA, graphB, vertexKey: str, silent: bool = False):
13370
+ def Imprint(graphA, graphB, vertexKeys, useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
13062
13371
  """
13063
- Intersect the two input graphs based on an input vertex key. See https://en.wikipedia.org/wiki/Boolean_operation.
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
- vertexKey : str
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.Intersect - Error: The graphA input parameter is not a valid graph. Returning None.")
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.Intersect - Error: The graphB input parameter is not a valid graph. Returning None.")
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 isinstance(vertexKey, str):
13517
+ if not Topology.IsInstance(graphB, "graph"):
13096
13518
  if not silent:
13097
- print("Graph.Intersect - Error: The vertexKey input parameter is not a valid string. Returning None.")
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
- for i, v in enumerate(vertices_a):
13125
- j = Graph._vertex_in_list(v, vertices_b, key=vertexKey)
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
- d_a = Topology.Dictionary(v)
13128
- d_b = Topology.Dictionary(vertices_b[j-1])
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
- v = Topology.SetDictionary(v, d_c, silent=True)
13131
- common_vertices.append(v)
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, key=vertexKey)
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, vertexKey: str, silent: bool = False):
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 an input vertex key. See https://en.wikipedia.org/wiki/Boolean_operation.
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
- vertexKey : str
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
- if not isinstance(vertexKey, str):
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, key=vertexKey)]
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, key=vertexKey):
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, key=vertexKey) and Graph._vertex_in_list(vertices_a[e[1]], diff_vertices, key=vertexKey):
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 SymmetricDifference(graphA, graphB, vertexKey: str, silent: bool = False):
13667
+ def Merge(graphA, graphB, vertexKeys=None, vertexColorKey="color", useCentroid: bool = False, tolerance: float = 0.0001, silent: bool = False):
13216
13668
  """
13217
- Find the symmetric difference pf the two input graphs based on an input vertex key. See https://en.wikipedia.org/wiki/Boolean_operation.
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
- vertexKey : str
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
- if not isinstance(vertexKey, str):
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.SymmetricDifference - Error: The vertexKey input parameter is not a valid string. Returning None.")
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, vertexKey=vertexKey, silent=True)
13252
- diffBA = Graph.Difference(graphB, graphA, vertexKey=vertexKey, silent=True)
13253
- return Graph.Union(diffAB, diffBA, vertexKey=vertexKey, silent=True)
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
- from topologicpy.Dictionary import Dictionary
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
- coords = np.array([[v.X(), v.Y(), v.Z()] for v in vertexList]) # Extract coordinates
1878
- tree = KDTree(coords) # Build k-d tree for efficient neighbor search
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
- for i, vertex in enumerate(coords):
1881
- neighbors = tree.query_ball_point(vertex, minDistance)
1882
- for neighbor_index in neighbors:
1883
- if neighbor_index != i: # Avoid self-comparison
1884
- direction = coords[neighbor_index] - vertex
1885
- distance = np.linalg.norm(direction)
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.39'
1
+ __version__ = '0.8.41'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.39
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=ZVVQRKGjebY9aOU1gpN_AbssdRRiVKlZV3f8TrsTNgg,20307
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=YLigYjWAAhMMtUurYngjK9NUMLkDM08BmYWYjxYwMcs,620455
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=Dmy9i9puSfvB42mrBUip3v0TCXIzHpAETWDUCDxfqNA,120556
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=G6KtKRKabV1gNUKCZ3rzZJkCogGkOJDnCl-_S7DuHKc,466648
28
+ topologicpy/Topology.py,sha256=hGjePUb2-1MmyMva9nNtPiWr4CAWMa__BGX17mIOHfA,467519
29
29
  topologicpy/Vector.py,sha256=X12eqskn28bdB7sLY1EZhq3noPYzPbNEgHPb4a959ss,42302
30
- topologicpy/Vertex.py,sha256=epBfbD7fm87T-TZ0WuwrioXdYqg9NgRlHn_qUFtVbkc,84562
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=krvNarxqLr-1e4M5aYKlWbcRZoZxDYVlZehTgEbpBTg,23
34
- topologicpy-0.8.39.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
35
- topologicpy-0.8.39.dist-info/METADATA,sha256=l1XGyTl1wZZ-G4PgjDEzyZ_kitEAFGAGhcgW8wV1Jos,10535
36
- topologicpy-0.8.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- topologicpy-0.8.39.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
38
- topologicpy-0.8.39.dist-info/RECORD,,
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,,