topologicpy 0.8.88__py3-none-any.whl → 0.8.90__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 +325 -31
- topologicpy/Wire.py +375 -7
- topologicpy/version.py +1 -1
- {topologicpy-0.8.88.dist-info → topologicpy-0.8.90.dist-info}/METADATA +1 -1
- {topologicpy-0.8.88.dist-info → topologicpy-0.8.90.dist-info}/RECORD +8 -8
- {topologicpy-0.8.88.dist-info → topologicpy-0.8.90.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.88.dist-info → topologicpy-0.8.90.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.88.dist-info → topologicpy-0.8.90.dist-info}/top_level.txt +0 -0
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
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
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
|
-
|
|
4928
|
-
|
|
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.
|
|
@@ -5028,7 +5251,8 @@ class Graph:
|
|
|
5028
5251
|
useInternalVertex: bool = False,
|
|
5029
5252
|
storeBREP: bool =False,
|
|
5030
5253
|
mantissa: int = 6,
|
|
5031
|
-
tolerance: float = 0.0001
|
|
5254
|
+
tolerance: float = 0.0001,
|
|
5255
|
+
silent: float = False):
|
|
5032
5256
|
"""
|
|
5033
5257
|
Creates a graph.See https://en.wikipedia.org/wiki/Graph_(discrete_mathematics).
|
|
5034
5258
|
|
|
@@ -5082,6 +5306,8 @@ class Graph:
|
|
|
5082
5306
|
If set to True, store the BRep of the subtopology in its representative vertex. Default is False.
|
|
5083
5307
|
tolerance : float , optional
|
|
5084
5308
|
The desired tolerance. Default is 0.0001.
|
|
5309
|
+
silent : bool , optional
|
|
5310
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
5085
5311
|
|
|
5086
5312
|
Returns
|
|
5087
5313
|
-------
|
|
@@ -5096,6 +5322,10 @@ class Graph:
|
|
|
5096
5322
|
from topologicpy.Topology import Topology
|
|
5097
5323
|
from topologicpy.Aperture import Aperture
|
|
5098
5324
|
|
|
5325
|
+
if not Topology.IsInstance(topology, "topology"):
|
|
5326
|
+
if not silent:
|
|
5327
|
+
print("Graph.ByTopology - Error: The input topology parameter is not a valid topology. Returning None.")
|
|
5328
|
+
return None
|
|
5099
5329
|
def _viaSharedTopologies(vt, sharedTops):
|
|
5100
5330
|
verts = []
|
|
5101
5331
|
eds = []
|
|
@@ -15354,7 +15584,7 @@ class Graph:
|
|
|
15354
15584
|
try:
|
|
15355
15585
|
gsv = Graph.NearestVertex(graph, vertexA)
|
|
15356
15586
|
gev = Graph.NearestVertex(graph, vertexB)
|
|
15357
|
-
shortest_path = graph.ShortestPath(gsv, gev, vertexKey, edgeKey)
|
|
15587
|
+
shortest_path = graph.ShortestPath(gsv, gev, vertexKey, edgeKey) # Hook to Core
|
|
15358
15588
|
if not shortest_path == None:
|
|
15359
15589
|
if Topology.IsInstance(shortest_path, "Edge"):
|
|
15360
15590
|
shortest_path = Wire.ByEdges([shortest_path])
|
|
@@ -15367,6 +15597,70 @@ class Graph:
|
|
|
15367
15597
|
except:
|
|
15368
15598
|
return None
|
|
15369
15599
|
|
|
15600
|
+
@staticmethod
|
|
15601
|
+
def shortestPathInFace(graph, face, vertexA, vertexB, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001, silent: bool = False):
|
|
15602
|
+
"""
|
|
15603
|
+
Returns the shortest path that connects the input vertices.
|
|
15604
|
+
|
|
15605
|
+
Parameters
|
|
15606
|
+
----------
|
|
15607
|
+
face : topologic_core.Face
|
|
15608
|
+
The input face. This is assumed to be planar and resting on the XY plane (z = 0)
|
|
15609
|
+
vertexA : topologic_core.Vertex
|
|
15610
|
+
The first input vertex.
|
|
15611
|
+
vertexB : topologic_core.Vertex
|
|
15612
|
+
The second input vertex.
|
|
15613
|
+
mode : int , optional
|
|
15614
|
+
The desired mode of meshing algorithm. Several options are available:
|
|
15615
|
+
0: Classic
|
|
15616
|
+
1: MeshAdapt
|
|
15617
|
+
3: Initial Mesh Only
|
|
15618
|
+
5: Delaunay
|
|
15619
|
+
6: Frontal-Delaunay
|
|
15620
|
+
7: BAMG
|
|
15621
|
+
8: Fontal-Delaunay for Quads
|
|
15622
|
+
9: Packing of Parallelograms
|
|
15623
|
+
All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
|
|
15624
|
+
WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
|
|
15625
|
+
meshSize : float , optional
|
|
15626
|
+
The desired size of the mesh when using the "mesh" option. If set to None, it will be
|
|
15627
|
+
calculated automatically and set to 10% of the overall size of the face.
|
|
15628
|
+
tolerance : float , optional
|
|
15629
|
+
The desired tolerance. Default is 0.0001.
|
|
15630
|
+
silent : bool , optional
|
|
15631
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
15632
|
+
|
|
15633
|
+
Returns
|
|
15634
|
+
-------
|
|
15635
|
+
topologic_core.Wire
|
|
15636
|
+
The shortest path between the input vertices.
|
|
15637
|
+
|
|
15638
|
+
"""
|
|
15639
|
+
from topologicpy.Wire import Wire
|
|
15640
|
+
from topologicpy.Topology import Topology
|
|
15641
|
+
|
|
15642
|
+
if not Topology.IsInstance(face, "face"):
|
|
15643
|
+
if not silent:
|
|
15644
|
+
print("Face.ShortestPath - Error: The input face parameter is not a topologic face. Returning None.")
|
|
15645
|
+
return None
|
|
15646
|
+
if not Topology.IsInstance(vertexA, "vertex"):
|
|
15647
|
+
if not silent:
|
|
15648
|
+
print("Face.ShortestPath - Error: The input vertexA parameter is not a topologic vertex. Returning None.")
|
|
15649
|
+
return None
|
|
15650
|
+
if not Topology.IsInstance(vertexB, "vertex"):
|
|
15651
|
+
if not silent:
|
|
15652
|
+
print("Face.ShortestPath - Error: The input vertexB parameter is not a topologic vertex. Returning None.")
|
|
15653
|
+
return None
|
|
15654
|
+
|
|
15655
|
+
sp = Graph.ShortestPath(graph, vertexA, vertexB)
|
|
15656
|
+
if sp == None:
|
|
15657
|
+
if not silent:
|
|
15658
|
+
print("Face.ShortestPath - Error: Could not find the shortest path. Returning None.")
|
|
15659
|
+
return None
|
|
15660
|
+
|
|
15661
|
+
new_path = Wire.StraightenInFace(sp, face, tolerance = tolerance)
|
|
15662
|
+
return new_path
|
|
15663
|
+
|
|
15370
15664
|
@staticmethod
|
|
15371
15665
|
def ShortestPaths(graph, vertexA, vertexB, vertexKey="", edgeKey="length", timeLimit=10,
|
|
15372
15666
|
pathLimit=10, tolerance=0.0001):
|
topologicpy/Wire.py
CHANGED
|
@@ -331,16 +331,19 @@ class Wire():
|
|
|
331
331
|
|
|
332
332
|
else:
|
|
333
333
|
best_br = boundingRectangle
|
|
334
|
-
x_min, y_min,
|
|
334
|
+
x_min, y_min, x_max, y_max = best_br
|
|
335
335
|
vb1 = Vertex.ByCoordinates(x_min, y_min, 0)
|
|
336
|
-
vb2 = Vertex.ByCoordinates(
|
|
337
|
-
vb3 = Vertex.ByCoordinates(
|
|
338
|
-
vb4 = Vertex.ByCoordinates(x_min,
|
|
336
|
+
vb2 = Vertex.ByCoordinates(x_max, y_min, 0)
|
|
337
|
+
vb3 = Vertex.ByCoordinates(x_max, y_max, 0)
|
|
338
|
+
vb4 = Vertex.ByCoordinates(x_min, y_max, 0)
|
|
339
339
|
|
|
340
340
|
boundingRectangle = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True, tolerance=tolerance, silent=silent)
|
|
341
341
|
boundingRectangle = Topology.Rotate(boundingRectangle, origin=origin, axis=[0, 0, 1], angle=-best_z)
|
|
342
342
|
boundingRectangle = Topology.Unflatten(boundingRectangle, origin=f_origin, direction=normal)
|
|
343
|
-
dictionary = Dictionary.ByKeysValues(["zrot"
|
|
343
|
+
dictionary = Dictionary.ByKeysValues(["zrot", "xmin", "ymin", "xmax", "ymax", "width", "length"],
|
|
344
|
+
[best_z, x_min, y_min, x_max, y_max, (x_max - x_min), (y_max - y_min)])
|
|
345
|
+
|
|
346
|
+
#dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
|
|
344
347
|
boundingRectangle = Topology.SetDictionary(boundingRectangle, dictionary)
|
|
345
348
|
return boundingRectangle
|
|
346
349
|
|
|
@@ -2426,7 +2429,254 @@ class Wire():
|
|
|
2426
2429
|
# Unflatten the wire
|
|
2427
2430
|
return_wire = Topology.Unflatten(flat_wire, origin=Vertex.Origin(), direction=normal)
|
|
2428
2431
|
return return_wire
|
|
2429
|
-
|
|
2432
|
+
|
|
2433
|
+
@staticmethod
|
|
2434
|
+
def Funnel(face,
|
|
2435
|
+
vertexA,
|
|
2436
|
+
vertexB,
|
|
2437
|
+
portals,
|
|
2438
|
+
tolerance: float = 0.0001,
|
|
2439
|
+
silent: float = False):
|
|
2440
|
+
"""
|
|
2441
|
+
Returns a Wire representing a smoothed path inside the given face using
|
|
2442
|
+
the funnel (string-pulling) algorithm.
|
|
2443
|
+
|
|
2444
|
+
The algorithm assumes that a corridor has already been computed, and is
|
|
2445
|
+
provided as an ordered list of "portals" (pairs of vertices) that lie
|
|
2446
|
+
on the face between the start and end locations.
|
|
2447
|
+
|
|
2448
|
+
Parameters
|
|
2449
|
+
----------
|
|
2450
|
+
face : topologic_core.Face
|
|
2451
|
+
The planar face on which navigation occurs. All vertices must lie
|
|
2452
|
+
on this face.
|
|
2453
|
+
vertexA : topologic_core.Vertex
|
|
2454
|
+
The start point of the path.
|
|
2455
|
+
vertexB : topologic_core.Vertex
|
|
2456
|
+
The end point of the path.
|
|
2457
|
+
portals : list of tuple(Vertex, Vertex)
|
|
2458
|
+
Ordered list of corridor edges. Each item is (leftVertex, rightVertex)
|
|
2459
|
+
describing the visible "portal" between two consecutive regions along
|
|
2460
|
+
the navmesh path.
|
|
2461
|
+
tolerance : float , optional
|
|
2462
|
+
Numerical tolerance used when comparing orientations and distances.
|
|
2463
|
+
Default is 0.0001.
|
|
2464
|
+
silent : bool , optional
|
|
2465
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
2466
|
+
|
|
2467
|
+
Returns
|
|
2468
|
+
-------
|
|
2469
|
+
wire : topologic_core.Wire
|
|
2470
|
+
A Wire representing the smoothed path from startVertex to endVertex
|
|
2471
|
+
that stays inside the navigation corridor on the face.
|
|
2472
|
+
"""
|
|
2473
|
+
from topologicpy.Dictionary import Dictionary
|
|
2474
|
+
from topologicpy.Vertex import Vertex
|
|
2475
|
+
from topologicpy.Face import Face
|
|
2476
|
+
from topologicpy.Topology import Topology
|
|
2477
|
+
|
|
2478
|
+
if not Topology.IsInstance(face, "face"):
|
|
2479
|
+
if not silent:
|
|
2480
|
+
print("Wire.Funnel - Error: The input face parameter is not a topologic face. Returning None.")
|
|
2481
|
+
return None
|
|
2482
|
+
if not Topology.IsInstance(vertexA, "vertex"):
|
|
2483
|
+
if not silent:
|
|
2484
|
+
print("Wire.Funnel - Error: The input vertexA parameter is not a topologic vertex. Returning None.")
|
|
2485
|
+
return None
|
|
2486
|
+
if not Topology.IsInstance(vertexB, "vertex"):
|
|
2487
|
+
if not silent:
|
|
2488
|
+
print("Wire.Funnel - Error: The input vertexB parameter is not a topologic vertex. Returning None.")
|
|
2489
|
+
return None
|
|
2490
|
+
|
|
2491
|
+
# ------------------------------------------------------------
|
|
2492
|
+
# 1. Basic helpers
|
|
2493
|
+
# ------------------------------------------------------------
|
|
2494
|
+
def _norm(v):
|
|
2495
|
+
return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
|
|
2496
|
+
|
|
2497
|
+
def _normalize(v):
|
|
2498
|
+
n = _norm(v)
|
|
2499
|
+
if n < tolerance:
|
|
2500
|
+
return (0.0, 0.0, 0.0)
|
|
2501
|
+
return (v[0] / n, v[1] / n, v[2] / n)
|
|
2502
|
+
|
|
2503
|
+
def _dot(a, b):
|
|
2504
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
|
2505
|
+
|
|
2506
|
+
def _cross(a, b):
|
|
2507
|
+
return (
|
|
2508
|
+
a[1] * b[2] - a[2] * b[1],
|
|
2509
|
+
a[2] * b[0] - a[0] * b[2],
|
|
2510
|
+
a[0] * b[1] - a[1] * b[0],
|
|
2511
|
+
)
|
|
2512
|
+
|
|
2513
|
+
def _sub(a, b):
|
|
2514
|
+
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
|
|
2515
|
+
|
|
2516
|
+
def _tri_area2(a2, b2, c2):
|
|
2517
|
+
"""
|
|
2518
|
+
Twice the signed area of triangle (a, b, c) in 2D.
|
|
2519
|
+
Positive => c is to the left of ab
|
|
2520
|
+
Negative => c is to the right of ab
|
|
2521
|
+
"""
|
|
2522
|
+
return (b2[0] - a2[0]) * (c2[1] - a2[1]) - (b2[1] - a2[1]) * (c2[0] - a2[0])
|
|
2523
|
+
|
|
2524
|
+
def _coords3d(v):
|
|
2525
|
+
x, y, z = Vertex.Coordinates(v)
|
|
2526
|
+
return (x, y, z)
|
|
2527
|
+
|
|
2528
|
+
# ------------------------------------------------------------
|
|
2529
|
+
# 2. Build a local 2D coordinate system on the face
|
|
2530
|
+
# ------------------------------------------------------------
|
|
2531
|
+
# Face normal
|
|
2532
|
+
n_vec = Face.Normal(face) # [nx, ny, nz]
|
|
2533
|
+
n = _normalize((n_vec[0], n_vec[1], n_vec[2]))
|
|
2534
|
+
|
|
2535
|
+
# Choose an arbitrary vector not parallel to n
|
|
2536
|
+
if abs(n[0]) < 0.9:
|
|
2537
|
+
arbitrary = (1.0, 0.0, 0.0)
|
|
2538
|
+
else:
|
|
2539
|
+
arbitrary = (0.0, 1.0, 0.0)
|
|
2540
|
+
|
|
2541
|
+
u = _normalize(_cross(n, arbitrary)) # tangent
|
|
2542
|
+
v = _cross(n, u) # bitangent, already orthogonal and normalized
|
|
2543
|
+
|
|
2544
|
+
def _project_to_2d(vertex):
|
|
2545
|
+
p = _coords3d(vertex)
|
|
2546
|
+
# project onto basis (u, v)
|
|
2547
|
+
return (_dot(p, u), _dot(p, v))
|
|
2548
|
+
|
|
2549
|
+
# Precompute 2D coords for start, end and all portal vertices
|
|
2550
|
+
start2d = _project_to_2d(vertexA)
|
|
2551
|
+
end2d = _project_to_2d(vertexB)
|
|
2552
|
+
|
|
2553
|
+
portal2d = []
|
|
2554
|
+
for l_v, r_v in portals:
|
|
2555
|
+
portal2d.append((_project_to_2d(l_v), _project_to_2d(r_v)))
|
|
2556
|
+
|
|
2557
|
+
# ------------------------------------------------------------
|
|
2558
|
+
# 3. Funnel algorithm in 2D
|
|
2559
|
+
# (based on classic Recast / string-pulling implementation)
|
|
2560
|
+
# ------------------------------------------------------------
|
|
2561
|
+
path_vertices = [vertexA]
|
|
2562
|
+
|
|
2563
|
+
apex2d = start2d
|
|
2564
|
+
apexVertex = vertexA
|
|
2565
|
+
apexIndex = -1
|
|
2566
|
+
|
|
2567
|
+
left2d = start2d
|
|
2568
|
+
right2d = start2d
|
|
2569
|
+
leftVertex = vertexA
|
|
2570
|
+
rightVertex = vertexB
|
|
2571
|
+
leftIndex = -1
|
|
2572
|
+
rightIndex = -1
|
|
2573
|
+
|
|
2574
|
+
n_portals = len(portals)
|
|
2575
|
+
i = 0
|
|
2576
|
+
|
|
2577
|
+
# We will process all portals, and then a final "portal" at the goal (end, end)
|
|
2578
|
+
while i <= n_portals:
|
|
2579
|
+
if i < n_portals:
|
|
2580
|
+
newLeft2d, newRight2d = portal2d[i]
|
|
2581
|
+
newLeftVertex, newRightVertex = portals[i]
|
|
2582
|
+
else:
|
|
2583
|
+
# last "portal" is the goal point itself
|
|
2584
|
+
newLeft2d = end2d
|
|
2585
|
+
newRight2d = end2d
|
|
2586
|
+
newLeftVertex = vertexB
|
|
2587
|
+
newRightVertex = vertexB
|
|
2588
|
+
|
|
2589
|
+
# --------------------------------------------------------
|
|
2590
|
+
# Update right side of funnel
|
|
2591
|
+
# --------------------------------------------------------
|
|
2592
|
+
area_apex_right_newRight = _tri_area2(apex2d, right2d, newRight2d)
|
|
2593
|
+
if area_apex_right_newRight <= tolerance:
|
|
2594
|
+
# New right vertex is "inside" or tightening the funnel
|
|
2595
|
+
area_apex_left_newRight = _tri_area2(apex2d, left2d, newRight2d)
|
|
2596
|
+
if (apexVertex == rightVertex) or (area_apex_left_newRight > tolerance):
|
|
2597
|
+
# Tighten the funnel on the right side
|
|
2598
|
+
right2d = newRight2d
|
|
2599
|
+
rightVertex = newRightVertex
|
|
2600
|
+
rightIndex = i
|
|
2601
|
+
else:
|
|
2602
|
+
# Right over left, so left becomes the new apex
|
|
2603
|
+
path_vertices.append(leftVertex)
|
|
2604
|
+
apex2d = _project_to_2d(leftVertex)
|
|
2605
|
+
apexVertex = leftVertex
|
|
2606
|
+
apexIndex = leftIndex
|
|
2607
|
+
|
|
2608
|
+
# Reset funnel
|
|
2609
|
+
left2d = apex2d
|
|
2610
|
+
right2d = apex2d
|
|
2611
|
+
leftVertex = apexVertex
|
|
2612
|
+
rightVertex = apexVertex
|
|
2613
|
+
leftIndex = apexIndex
|
|
2614
|
+
rightIndex = apexIndex
|
|
2615
|
+
|
|
2616
|
+
# Restart from the new apex
|
|
2617
|
+
i = apexIndex + 1
|
|
2618
|
+
continue
|
|
2619
|
+
|
|
2620
|
+
# --------------------------------------------------------
|
|
2621
|
+
# Update left side of funnel
|
|
2622
|
+
# --------------------------------------------------------
|
|
2623
|
+
area_apex_left_newLeft = _tri_area2(apex2d, left2d, newLeft2d)
|
|
2624
|
+
if area_apex_left_newLeft >= -tolerance:
|
|
2625
|
+
# New left vertex is "inside" or tightening the funnel
|
|
2626
|
+
area_apex_right_newLeft = _tri_area2(apex2d, right2d, newLeft2d)
|
|
2627
|
+
if (apexVertex == leftVertex) or (area_apex_right_newLeft < -tolerance):
|
|
2628
|
+
# Tighten funnel on the left side
|
|
2629
|
+
left2d = newLeft2d
|
|
2630
|
+
leftVertex = newLeftVertex
|
|
2631
|
+
leftIndex = i
|
|
2632
|
+
else:
|
|
2633
|
+
# Left over right, so right becomes the new apex
|
|
2634
|
+
path_vertices.append(rightVertex)
|
|
2635
|
+
apex2d = _project_to_2d(rightVertex)
|
|
2636
|
+
apexVertex = rightVertex
|
|
2637
|
+
apexIndex = rightIndex
|
|
2638
|
+
|
|
2639
|
+
# Reset funnel
|
|
2640
|
+
left2d = apex2d
|
|
2641
|
+
right2d = apex2d
|
|
2642
|
+
leftVertex = apexVertex
|
|
2643
|
+
rightVertex = apexVertex
|
|
2644
|
+
leftIndex = apexIndex
|
|
2645
|
+
rightIndex = apexIndex
|
|
2646
|
+
|
|
2647
|
+
# Restart from the new apex
|
|
2648
|
+
i = apexIndex + 1
|
|
2649
|
+
continue
|
|
2650
|
+
|
|
2651
|
+
i += 1
|
|
2652
|
+
|
|
2653
|
+
# Finally, add the end point if it is not already in the path
|
|
2654
|
+
if path_vertices[-1] is not vertexB:
|
|
2655
|
+
path_vertices.append(vertexB)
|
|
2656
|
+
|
|
2657
|
+
# ------------------------------------------------------------
|
|
2658
|
+
# 4. Build and return the Topologic wire
|
|
2659
|
+
# ------------------------------------------------------------
|
|
2660
|
+
return_wire = Wire.ByVertices(path_vertices, close=False, silent=True)
|
|
2661
|
+
bb = Wire.BoundingRectangle(face)
|
|
2662
|
+
d = Topology.Dictionary(bb)
|
|
2663
|
+
width = Dictionary.ValueAtKey(d, "width")
|
|
2664
|
+
length = Dictionary.ValueAtKey(d, "length")
|
|
2665
|
+
size = max(width, length)
|
|
2666
|
+
percentage = 0.25 # Start with 25% of the total size
|
|
2667
|
+
is_ok = False
|
|
2668
|
+
while is_ok == False and percentage > 0:
|
|
2669
|
+
new_wire = Wire.Simplify(return_wire, tolerance=size*percentage, silent=True)
|
|
2670
|
+
test_wire = Topology.Scale(new_wire, Topology.Centroid(new_wire), 0.95, 0.95, 1)
|
|
2671
|
+
result = Topology.Difference(test_wire, face, tolerance=tolerance, silent=True)
|
|
2672
|
+
if result is None:
|
|
2673
|
+
is_ok = True
|
|
2674
|
+
return_wire = new_wire
|
|
2675
|
+
percentage -= 0.01
|
|
2676
|
+
print("Wire.Funnel - Result:", result)
|
|
2677
|
+
print("Wire.Funnel - Percentage:", percentage)
|
|
2678
|
+
return new_wire
|
|
2679
|
+
|
|
2430
2680
|
@staticmethod
|
|
2431
2681
|
def InteriorAngles(wire, tolerance: float = 0.0001, mantissa: int = 6) -> list:
|
|
2432
2682
|
"""
|
|
@@ -4188,7 +4438,7 @@ class Wire():
|
|
|
4188
4438
|
if not silent:
|
|
4189
4439
|
print("Wire.Simplify - Warning: Could not generate enough vertices for a simplified wire. Returning the original wire.")
|
|
4190
4440
|
wire
|
|
4191
|
-
new_wire = Wire.ByVertices(new_vertices, close=Wire.IsClosed(wire), tolerance=tolerance)
|
|
4441
|
+
new_wire = Wire.ByVertices(new_vertices, close=Wire.IsClosed(wire), tolerance=tolerance, silent=True)
|
|
4192
4442
|
if not Topology.IsInstance(new_wire, "wire"):
|
|
4193
4443
|
if not silent:
|
|
4194
4444
|
print("Wire.Simplify - Warning: Could not generate a simplified wire. Returning the original wire.")
|
|
@@ -4713,6 +4963,124 @@ class Wire():
|
|
|
4713
4963
|
sv, ev = Wire.StartEndVertices(wire, silent=silent)
|
|
4714
4964
|
return sv
|
|
4715
4965
|
|
|
4966
|
+
@staticmethod
|
|
4967
|
+
def StraightenInFace(wire, face, tolerance: float = 0.0001):
|
|
4968
|
+
"""
|
|
4969
|
+
Returns a new Wire obtained by recursively replacing segments of the
|
|
4970
|
+
input wire with the longest possible straight edge that is fully
|
|
4971
|
+
embedded in the given face.
|
|
4972
|
+
|
|
4973
|
+
For each starting vertex v_i along the wire, this method searches for
|
|
4974
|
+
the furthest vertex v_j (j > i) such that the straight Edge between
|
|
4975
|
+
v_i and v_j satisfies:
|
|
4976
|
+
|
|
4977
|
+
Topology.Difference(edge, face) == None
|
|
4978
|
+
|
|
4979
|
+
i.e. the edge lies completely within (or on the boundary of) the face.
|
|
4980
|
+
All edges of the original wire between vertex indices i and j are then
|
|
4981
|
+
replaced by this straight edge, and the process is repeated recursively
|
|
4982
|
+
from index j.
|
|
4983
|
+
|
|
4984
|
+
Parameters
|
|
4985
|
+
----------
|
|
4986
|
+
wire : topologic_core.Wire
|
|
4987
|
+
The input path wire whose vertices define the route to be
|
|
4988
|
+
straightened.
|
|
4989
|
+
face : topologic_core.Face
|
|
4990
|
+
The face within which the straightened edges must lie.
|
|
4991
|
+
tolerance : float , optional
|
|
4992
|
+
Numerical tolerance used for internal robustness checks. The
|
|
4993
|
+
Topology.Difference call itself is left with its default tolerance.
|
|
4994
|
+
Default is 1e-6.
|
|
4995
|
+
|
|
4996
|
+
Returns
|
|
4997
|
+
-------
|
|
4998
|
+
wire : topologic_core.Wire
|
|
4999
|
+
A new Wire whose vertices define the recursively straightened path.
|
|
5000
|
+
"""
|
|
5001
|
+
from topologicpy.Vertex import Vertex
|
|
5002
|
+
from topologicpy.Edge import Edge
|
|
5003
|
+
from topologicpy.Wire import Wire
|
|
5004
|
+
from topologicpy.Face import Face
|
|
5005
|
+
from topologicpy.Topology import Topology
|
|
5006
|
+
# Get ordered vertices of the wire
|
|
5007
|
+
vertices = Topology.Vertices(wire)
|
|
5008
|
+
n = len(vertices)
|
|
5009
|
+
|
|
5010
|
+
if n <= 2:
|
|
5011
|
+
# Nothing to straighten
|
|
5012
|
+
return wire
|
|
5013
|
+
|
|
5014
|
+
def _edge_inside_face(v_start, v_end):
|
|
5015
|
+
"""
|
|
5016
|
+
Returns True if the straight edge between v_start and v_end is
|
|
5017
|
+
fully embedded in the face, i.e. Topology.Difference(edge, face)
|
|
5018
|
+
returns None.
|
|
5019
|
+
"""
|
|
5020
|
+
if v_start is v_end:
|
|
5021
|
+
return True
|
|
5022
|
+
edge = Edge.ByStartVertexEndVertex(v_start, v_end)
|
|
5023
|
+
diff = Topology.Difference(edge, face)
|
|
5024
|
+
return diff is None
|
|
5025
|
+
|
|
5026
|
+
def _find_longest_valid_index(start_idx):
|
|
5027
|
+
"""
|
|
5028
|
+
For a fixed start_idx, search for the largest index j >= start_idx+1
|
|
5029
|
+
such that the direct edge (vertices[start_idx], vertices[j]) is
|
|
5030
|
+
fully inside the face.
|
|
5031
|
+
|
|
5032
|
+
If for any reason no such j exists (which should not happen if the
|
|
5033
|
+
original wire lies in the face), it falls back to start_idx + 1.
|
|
5034
|
+
"""
|
|
5035
|
+
v_start = vertices[start_idx]
|
|
5036
|
+
best_j = None
|
|
5037
|
+
|
|
5038
|
+
for j in range(start_idx + 1, n):
|
|
5039
|
+
v_end = vertices[j]
|
|
5040
|
+
if _edge_inside_face(v_start, v_end):
|
|
5041
|
+
best_j = j
|
|
5042
|
+
# Do NOT break on failure: a further vertex might still
|
|
5043
|
+
# be reachable by a straight edge that stays in the face.
|
|
5044
|
+
|
|
5045
|
+
if best_j is None:
|
|
5046
|
+
# Fallback: use the immediate next vertex to avoid stalling
|
|
5047
|
+
best_j = min(start_idx + 1, n - 1)
|
|
5048
|
+
|
|
5049
|
+
return best_j
|
|
5050
|
+
|
|
5051
|
+
def _straighten_recursive(start_idx, out_vertices):
|
|
5052
|
+
"""
|
|
5053
|
+
Recursive helper.
|
|
5054
|
+
|
|
5055
|
+
Appends the chosen vertices to out_vertices. At each step, it
|
|
5056
|
+
decides how far it can jump from start_idx with a single straight
|
|
5057
|
+
edge inside the face, then recurses from that new index.
|
|
5058
|
+
"""
|
|
5059
|
+
# Base case: we are at the last vertex
|
|
5060
|
+
if start_idx == n - 1:
|
|
5061
|
+
out_vertices.append(vertices[start_idx])
|
|
5062
|
+
return
|
|
5063
|
+
|
|
5064
|
+
# Find furthest valid index reachable from start_idx
|
|
5065
|
+
next_idx = _find_longest_valid_index(start_idx)
|
|
5066
|
+
|
|
5067
|
+
# Add the starting vertex for this segment
|
|
5068
|
+
out_vertices.append(vertices[start_idx])
|
|
5069
|
+
|
|
5070
|
+
# Recurse from the chosen furthest index
|
|
5071
|
+
_straighten_recursive(next_idx, out_vertices)
|
|
5072
|
+
|
|
5073
|
+
# Run the recursion
|
|
5074
|
+
new_vertices = []
|
|
5075
|
+
_straighten_recursive(0, new_vertices)
|
|
5076
|
+
|
|
5077
|
+
# In case of any numerical quirks, ensure the last original vertex is present
|
|
5078
|
+
if new_vertices[-1] is not vertices[-1]:
|
|
5079
|
+
new_vertices.append(vertices[-1])
|
|
5080
|
+
|
|
5081
|
+
# Build the new straightened wire
|
|
5082
|
+
return Wire.ByVertices(new_vertices, close=False)
|
|
5083
|
+
|
|
4716
5084
|
@staticmethod
|
|
4717
5085
|
def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
|
|
4718
5086
|
"""
|
topologicpy/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.8.
|
|
1
|
+
__version__ = '0.8.90'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topologicpy
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.90
|
|
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=
|
|
15
|
+
topologicpy/Graph.py,sha256=c-n_9xKXizbyGGWBw62fECsqMo-UoqvQJz6nw82DWgA,811337
|
|
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
|
|
@@ -29,11 +29,11 @@ topologicpy/Sun.py,sha256=ezisiHfc2nd7A_8w0Ykq2VgbS0A9WNSg-tBwvfTQAVM,36735
|
|
|
29
29
|
topologicpy/Topology.py,sha256=E_AyPPCIx_Eq-UT74QS3LKFXIwdwekRjJJGTo1CRMRY,548577
|
|
30
30
|
topologicpy/Vector.py,sha256=pEC8YY3TeHGfGdeNgvdHjgMDwxGabp5aWjwYC1HSvMk,42236
|
|
31
31
|
topologicpy/Vertex.py,sha256=26TrlX9OCZUN-lMlZG3g4RHTWBqw69NW4AOEgRz_YMo,91269
|
|
32
|
-
topologicpy/Wire.py,sha256=
|
|
32
|
+
topologicpy/Wire.py,sha256=Rhqw0CGEWIMVL1ICQqkCp9G-VnhhHLhEiDDR00fAn_s,248919
|
|
33
33
|
topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
|
|
34
|
-
topologicpy/version.py,sha256=
|
|
35
|
-
topologicpy-0.8.
|
|
36
|
-
topologicpy-0.8.
|
|
37
|
-
topologicpy-0.8.
|
|
38
|
-
topologicpy-0.8.
|
|
39
|
-
topologicpy-0.8.
|
|
34
|
+
topologicpy/version.py,sha256=Vg4EZKvQvJEbHjPwL_FRDJq1bK9YEoQB3n1ijcuv9Dk,23
|
|
35
|
+
topologicpy-0.8.90.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
|
36
|
+
topologicpy-0.8.90.dist-info/METADATA,sha256=Zk31JGzXHHQe7HU4kLuQ6SvCj6EnOW3PolmmmV1EepE,10535
|
|
37
|
+
topologicpy-0.8.90.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
topologicpy-0.8.90.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
|
39
|
+
topologicpy-0.8.90.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|