topologicpy 0.8.88__py3-none-any.whl → 0.8.89__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/Graph.py CHANGED
@@ -4756,12 +4756,13 @@ class Graph:
4756
4756
  include: list = ["contains", "coveredBy", "covers", "crosses", "disjoint", "equals", "overlaps", "touches","within", "proximity"],
4757
4757
  proximityValues = [1, 5, 10],
4758
4758
  proximityLabels = ["near", "intermediate", "far"],
4759
- useInternalVertex = False,
4760
- vertexIDKey = "id",
4761
- edgeKeyFwd = "relFwd",
4762
- edgeKeyBwd = "relBwd",
4763
- connectsKey = "connects",
4764
- storeBREP = False,
4759
+ useShortestDistance: bool = False,
4760
+ useInternalVertex: bool = False,
4761
+ vertexIDKey: str = "id",
4762
+ edgeKeyFwd: str = "relFwd",
4763
+ edgeKeyBwd: str = "relBwd",
4764
+ connectsKey:str = "connects",
4765
+ storeBREP: bool = False,
4765
4766
  mantissa: int = 6,
4766
4767
  tolerance: float = 0.0001,
4767
4768
  silent: bool = False
@@ -4785,7 +4786,8 @@ class Graph:
4785
4786
  proximityLabels: list , optional
4786
4787
  The list of range labels (e.g. "near", "intermediate", "far") that correspond to the proximityValues list.
4787
4788
  The list must have the same number of elements as the proximityValues list. Default is ["near", "intermediate", "far"]
4788
-
4789
+ useShortestDistance: bool , optional
4790
+ If set to True, the shortest distance between objects is used. Otherwise, the distance between their centroids is used. Default is False.
4789
4791
  useInternalVertex: bool , optional
4790
4792
  If set to True, an internal vertex of the represented topology will be used as a graph node.
4791
4793
  Otherwise, its centroid will be used. Default is False.
@@ -4814,6 +4816,7 @@ class Graph:
4814
4816
  """
4815
4817
  from topologicpy.Graph import Graph
4816
4818
  from topologicpy.BVH import BVH
4819
+ from topologicpy.Cell import Cell
4817
4820
  from topologicpy.Vertex import Vertex
4818
4821
  from topologicpy.Topology import Topology
4819
4822
  from topologicpy.Dictionary import Dictionary
@@ -4850,6 +4853,234 @@ class Graph:
4850
4853
  v = Topology.SetDictionary(v, Topology.Dictionary(topologyList[0]))
4851
4854
  return Graph.ByVerticesEdges([v], [])
4852
4855
 
4856
+ # ---------- Calculate Proximity ------------
4857
+
4858
+ def _calc_proximity(topologies: list,
4859
+ ranges: list,
4860
+ labels: list,
4861
+ useShortestDistance: bool = True,
4862
+ tolerance: float = 0.0001,
4863
+ silent: bool = False):
4864
+ """
4865
+ Creates a proximity graph from a list of topologies using a BVH
4866
+ to prune distance checks for large input sets.
4867
+
4868
+ Each topology is represented by a vertex in the graph.
4869
+ An edge is created between two vertices if the distance between
4870
+ their corresponding topologies is <= max(ranges).
4871
+
4872
+ A BVH is used as a broad-phase accelerator: for each topology,
4873
+ a query box centred at its centroid and sized to cover the
4874
+ maximum proximity range is used to retrieve only nearby
4875
+ candidates. Exact distances are then computed only for those
4876
+ candidates.
4877
+
4878
+ Parameters
4879
+ ----------
4880
+ topologies : list of topologic_core.Topology
4881
+ The input topologies to be represented as vertices.
4882
+ ranges : list of float
4883
+ A list of positive numeric thresholds (e.g. [1.0, 3.0, 5.0]).
4884
+ Interpreted as upper bounds of proximity bands.
4885
+ Distances larger than max(ranges) are ignored (no edge).
4886
+ labels : list of str
4887
+ A list of proximity labels, same length as `ranges`.
4888
+ For a pair distance d:
4889
+ - d <= ranges[0] -> labels[0]
4890
+ - ranges[0] < d <= ranges[1] -> labels[1]
4891
+ - ...
4892
+ useShortestDistance : bool , optional
4893
+ If True, use Topology.ShortestDistance(topologyA, topologyB)
4894
+ if available. If False (or if that fails), fall back to the
4895
+ distance between the centroids of the two topologies.
4896
+ Default is True.
4897
+ tolerance : float , optional
4898
+ A small numeric tolerance used when comparing distances to
4899
+ range bounds. Default is 0.0001.
4900
+ silent : bool , optional
4901
+ If False, basic sanity-check warnings are printed.
4902
+ Default is False.
4903
+
4904
+ Returns
4905
+ -------
4906
+ graph : topologic_core.Graph
4907
+ A graph whose vertices correspond to the input topologies
4908
+ and whose edges connect topologies that fall within the
4909
+ supplied distance ranges. Each edge dictionary contains:
4910
+ - "distance" : float (actual distance)
4911
+ - "proximity" : str (label from `labels`)
4912
+ - "range_max" : float (upper bound used for the bin)
4913
+ - "source_index" : int
4914
+ - "target_index" : int
4915
+
4916
+ Notes
4917
+ -----
4918
+ - Complexity is approximately O(n log n + k) where k is the
4919
+ number of candidate pairs returned by the BVH.
4920
+ - BVH is used only as a broad-phase filter; exact distance
4921
+ tests still guarantee correctness with respect to `ranges`.
4922
+ """
4923
+
4924
+ # Basic validation
4925
+ if not isinstance(topologies, list) or len(topologies) < 2:
4926
+ if not silent:
4927
+ print("Graph.BySpatialRelationships - Error: Need a list of at least two topologies.")
4928
+ return None
4929
+
4930
+ if not isinstance(ranges, list) or not isinstance(labels, list):
4931
+ if not silent:
4932
+ print("Graph.BySpatialRelationships - Error: 'proximityValues' and 'proximityLabels' must be lists.")
4933
+ return None
4934
+
4935
+ if len(ranges) == 0 or len(ranges) != len(labels):
4936
+ if not silent:
4937
+ print("Graph.BySpatialRelationships - Error: 'proximityValues' must be non-empty and "
4938
+ "have the same length as 'labels'.")
4939
+ return None
4940
+
4941
+ # Sort ranges and labels together (ascending by range)
4942
+ try:
4943
+ rl = sorted(zip(ranges, labels), key=lambda x: x[0])
4944
+ except Exception:
4945
+ if not silent:
4946
+ print("Graph.BySpatialRelationships - Error: Could not sort ranges; check they are numeric.")
4947
+ return None
4948
+
4949
+ sorted_ranges = [r for (r, _) in rl]
4950
+ sorted_labels = [lab for (_, lab) in rl]
4951
+
4952
+ max_range = sorted_ranges[-1]
4953
+
4954
+ # Precompute representative vertices (centroids) for each topology
4955
+ vertices = []
4956
+ n = len(topologies)
4957
+ for i, topo in enumerate(topologies):
4958
+ if not topo:
4959
+ if not silent:
4960
+ print(f"Graph.BySpatialRelationships - Warning: Ignoring None topology at index {i}.")
4961
+ vertices.append(None)
4962
+ continue
4963
+ try:
4964
+ c_vtx = Topology.Centroid(topo)
4965
+ except Exception:
4966
+ # Fallback if centroid fails
4967
+ if not silent:
4968
+ print(f"Graph.BySpatialRelationships - Error: Failed to compute centroid for topology {i}, "
4969
+ f"using origin as placeholder.")
4970
+ c_vtx = Vertex.ByCoordinates(0, 0, 0)
4971
+
4972
+ # Attach index dictionary to the vertex (not to the original topology)
4973
+ d_keys = ["index"]
4974
+ d_vals = [i]
4975
+ v_dict = Dictionary.ByKeysValues(d_keys, d_vals)
4976
+ c_vtx = Topology.SetDictionary(c_vtx, v_dict)
4977
+ vertices.append(c_vtx)
4978
+
4979
+ # Build BVH on the original topologies
4980
+ try:
4981
+ bvh = BVH.ByTopologies(topologies)
4982
+ except Exception as e:
4983
+ if not silent:
4984
+ print(f"Graph.BySpatialRelationships - Error: Failed to build BVH, falling back to O(n^2): {e}")
4985
+ # Fallback: use the non-BVH variant if you like,
4986
+ # or just early-return None. Here we just bail out.
4987
+ return None
4988
+
4989
+ # Map from topology identity to index for fast lookup
4990
+ id_to_index = {id(topo): i for i, topo in enumerate(topologies)}
4991
+
4992
+ # Helper to compute distance between two topologies
4993
+ def _distance(topoA, topoB, vA, vB):
4994
+ d_val = None
4995
+ if useShortestDistance:
4996
+ try:
4997
+ d_val = Topology.ShortestDistance(topoA, topoB)
4998
+ except Exception:
4999
+ d_val = None
5000
+ if d_val is None:
5001
+ try:
5002
+ d_val = Vertex.Distance(vA, vB)
5003
+ except Exception:
5004
+ d_val = None
5005
+ return d_val
5006
+
5007
+ edges = []
5008
+
5009
+ # Main loop: for each topology, query BVH for candidates within
5010
+ # a bounding box of size 2*max_range around its centroid.
5011
+ for i in range(n):
5012
+ topo_i = topologies[i]
5013
+ v_i = vertices[i]
5014
+ if topo_i is None or v_i is None:
5015
+ continue
5016
+
5017
+ # Build a query box centered at the centroid with size 2 * max_range
5018
+ try:
5019
+ query_box = Cell.Prism(
5020
+ origin=v_i,
5021
+ width=2 * max_range,
5022
+ length=2 * max_range,
5023
+ height=2 * max_range
5024
+ )
5025
+ except Exception as q_err:
5026
+ if not silent:
5027
+ print(f"Graph.BySpatialRelationships - Error: Failed to build query box for {i}: {q_err}")
5028
+ continue
5029
+
5030
+ try:
5031
+ candidates = BVH.Clashes(bvh, query_box)
5032
+ except Exception as c_err:
5033
+ if not silent:
5034
+ print(f"Graph.BySpatialRelationships - Error: BVH.Clashes failed for {i}: {c_err}")
5035
+ continue
5036
+
5037
+ if not candidates:
5038
+ continue
5039
+
5040
+ for cand in candidates:
5041
+ j = id_to_index.get(id(cand), None)
5042
+ if j is None:
5043
+ continue
5044
+ # Enforce i < j to avoid duplicate edges
5045
+ if j <= i:
5046
+ continue
5047
+
5048
+ topo_j = topologies[j]
5049
+ v_j = vertices[j]
5050
+ if topo_j is None or v_j is None:
5051
+ continue
5052
+
5053
+ # Compute exact distance
5054
+ d = _distance(topo_i, topo_j, v_i, v_j)
5055
+ if d is None:
5056
+ if not silent:
5057
+ print(f"Graph.BySpatialRelationships - Error: Could not compute distance between "
5058
+ f"{i} and {j}.")
5059
+ continue
5060
+
5061
+ # Skip if beyond max range (plus tolerance)
5062
+ if d > max_range + tolerance:
5063
+ continue
5064
+
5065
+ # Bin the distance into the appropriate range/label
5066
+ label = None
5067
+ range_max = None
5068
+ for r, lab in zip(sorted_ranges, sorted_labels):
5069
+ if d <= r + tolerance:
5070
+ label = lab
5071
+ range_max = r
5072
+ break
5073
+
5074
+ if label is None:
5075
+ continue
5076
+
5077
+ e_keys = ["distance", "proximity", "range_max", "source_index", "target_index"]
5078
+ e_values = [float(d), str(label), float(range_max), i, j]
5079
+ d = Dictionary.ByKeysValues(e_keys, e_values)
5080
+ edges.append(d)
5081
+
5082
+ return edges
5083
+
4853
5084
  # ---------- BVH once ----------
4854
5085
  bvh = BVH.ByTopologies(topologyList, silent=True)
4855
5086
 
@@ -4924,31 +5155,23 @@ class Graph:
4924
5155
  ))
4925
5156
 
4926
5157
  # ---------- main loops (each unordered pair once) ----------
4927
- used = []
4928
- proximity_edges = []
5158
+ if "proximity" in include:
5159
+ prox_dicts = _calc_proximity(topologies = topologyList,
5160
+ ranges = proximityValues,
5161
+ labels = proximityLabels,
5162
+ useShortestDistance = useShortestDistance,
5163
+ tolerance = tolerance,
5164
+ silent = silent)
5165
+ for prox_dict in prox_dicts:
5166
+ ai = Dictionary.ValueAtKey(prox_dict, "source_index")
5167
+ bj = Dictionary.ValueAtKey(prox_dict, "target_index")
5168
+ rel = Dictionary.ValueAtKey(prox_dict, "proximity")
5169
+ if (rel in proximityLabels):
5170
+ _add_edge(ai, bj, rel)
5171
+
4929
5172
  for i, a in enumerate(topologyList):
4930
5173
  candidates = []
4931
5174
  ai = i
4932
- if "proximity" in include:
4933
- for j, b in enumerate(topologyList):
4934
- bj = index_of.get(id(b))
4935
- if i == bj or (i,bj) in used or (bj,i) in used:
4936
- continue
4937
- else:
4938
- used.append((i,bj))
4939
- used.append((bj,i))
4940
- rel = Topology.SpatialRelationship( a,
4941
- b,
4942
- include=["proximity"],
4943
- proximityValues = proximityValues,
4944
- proximityLabels = proximityLabels,
4945
- mantissa=mantissa,
4946
- tolerance=tolerance,
4947
- silent=True
4948
- )
4949
- rel_ok = (rel in proximityLabels)
4950
- if rel_ok:
4951
- _add_edge(ai, bj, rel)
4952
5175
  candidates = BVH.Clashes(bvh, a) or []
4953
5176
  if not candidates:
4954
5177
  # If you want to connect "disjoint" to *all* non-candidates, that would be O(n) per i.
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.8.88'
1
+ __version__ = '0.8.89'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.88
3
+ Version: 0.8.89
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
@@ -12,7 +12,7 @@ topologicpy/Dictionary.py,sha256=goODXIM6AoC5Qn_d8LGc5pRoxZKgIWbkn3IOEbsQ4c4,449
12
12
  topologicpy/Edge.py,sha256=aiRd1xZgG2GGYHxva0bM-kDy3AVmwGA_S2pMur8EeMg,74911
13
13
  topologicpy/EnergyModel.py,sha256=MEai1GF1hINeH5bhclJj_lpMU3asFTvW2RlPm40GNj4,57794
14
14
  topologicpy/Face.py,sha256=qAl36LcwiyRclMM2pI9NyWHzmgNlaykXiJx1wu10RmA,201317
15
- topologicpy/Graph.py,sha256=oDzAX_lmM10ayRA8mNenlccJu-oe-FYwW2GHbAp9x5s,798255
15
+ topologicpy/Graph.py,sha256=AfX4M_55zGBOm5RwpVEkr3JHv-hITFh9ckW1_jsLxFs,808052
16
16
  topologicpy/Grid.py,sha256=3OsBMyHh4w8gpFOTMKHMNTpo62V0CwRNu5cwm87yDUA,18421
17
17
  topologicpy/Helper.py,sha256=NsmMlbbKFPRX6jfoko-ZQVQ7MBsfVp9FD0ZvC2U7q-8,32002
18
18
  topologicpy/Honeybee.py,sha256=dBk01jIvxjQMGHqSarM1Cukv16ot4Op7Dwlitn2OMoc,48990
@@ -31,9 +31,9 @@ topologicpy/Vector.py,sha256=pEC8YY3TeHGfGdeNgvdHjgMDwxGabp5aWjwYC1HSvMk,42236
31
31
  topologicpy/Vertex.py,sha256=26TrlX9OCZUN-lMlZG3g4RHTWBqw69NW4AOEgRz_YMo,91269
32
32
  topologicpy/Wire.py,sha256=au0ZkuuZgVzHYE5E1fRwflRT3win0yTivHKOhonAzUk,234116
33
33
  topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
34
- topologicpy/version.py,sha256=qAXCd2Ric5BF00XcziW6Y7EfWidI5Yii7_q1vhA8if4,23
35
- topologicpy-0.8.88.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
36
- topologicpy-0.8.88.dist-info/METADATA,sha256=TmanTsL4EPrwo0Fd1oWarCyWzj91C3AWRbJE0LMxbag,10535
37
- topologicpy-0.8.88.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- topologicpy-0.8.88.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
39
- topologicpy-0.8.88.dist-info/RECORD,,
34
+ topologicpy/version.py,sha256=YhsAc7e0YNllH7mN5LamhWpA_q-FmHWAFByeNTu5xTk,23
35
+ topologicpy-0.8.89.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
36
+ topologicpy-0.8.89.dist-info/METADATA,sha256=55cNepIhl59TBcxtlcTmihxcT8dcait0fIEuKHXUq3A,10535
37
+ topologicpy-0.8.89.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ topologicpy-0.8.89.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
39
+ topologicpy-0.8.89.dist-info/RECORD,,