topologicpy 0.7.87__py3-none-any.whl → 0.7.91__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/Cell.py +7 -3
- topologicpy/Face.py +175 -18
- topologicpy/Graph.py +765 -114
- topologicpy/Plotly.py +43 -22
- topologicpy/Topology.py +19 -16
- topologicpy/version.py +1 -1
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/METADATA +1 -1
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/RECORD +11 -11
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/top_level.txt +0 -0
topologicpy/Cell.py
CHANGED
@@ -1471,7 +1471,7 @@ class Cell():
|
|
1471
1471
|
return shells
|
1472
1472
|
|
1473
1473
|
@staticmethod
|
1474
|
-
def InternalVertex(cell, tolerance: float = 0.0001):
|
1474
|
+
def InternalVertex(cell, tolerance: float = 0.0001, silent: bool = False):
|
1475
1475
|
"""
|
1476
1476
|
Creates a vertex that is guaranteed to be inside the input cell.
|
1477
1477
|
|
@@ -1481,6 +1481,8 @@ class Cell():
|
|
1481
1481
|
The input cell.
|
1482
1482
|
tolerance : float , optional
|
1483
1483
|
The desired tolerance. The default is 0.0001.
|
1484
|
+
silent : bool , optional
|
1485
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1484
1486
|
|
1485
1487
|
Returns
|
1486
1488
|
-------
|
@@ -1491,12 +1493,14 @@ class Cell():
|
|
1491
1493
|
from topologicpy.Topology import Topology
|
1492
1494
|
|
1493
1495
|
if not Topology.IsInstance(cell, "Cell"):
|
1494
|
-
|
1496
|
+
if not silent:
|
1497
|
+
print("Cell.InternalVertex - Error: The input cell parameter is not a valid topologic cell. Returning None.")
|
1495
1498
|
return None
|
1496
1499
|
try:
|
1497
1500
|
return topologic.CellUtility.InternalVertex(cell, tolerance) # Hook to Core
|
1498
1501
|
except:
|
1499
|
-
|
1502
|
+
if not silent:
|
1503
|
+
print("Cell.InternalVertex - Error: Could not create an internal vertex. Returning None.")
|
1500
1504
|
return None
|
1501
1505
|
|
1502
1506
|
@staticmethod
|
topologicpy/Face.py
CHANGED
@@ -1518,7 +1518,7 @@ class Face():
|
|
1518
1518
|
return list(wires)
|
1519
1519
|
|
1520
1520
|
@staticmethod
|
1521
|
-
def InternalVertex(face, tolerance: float = 0.0001):
|
1521
|
+
def InternalVertex(face, tolerance: float = 0.0001, silent: bool = False):
|
1522
1522
|
"""
|
1523
1523
|
Creates a vertex guaranteed to be inside the input face.
|
1524
1524
|
|
@@ -1528,6 +1528,8 @@ class Face():
|
|
1528
1528
|
The input face.
|
1529
1529
|
tolerance : float , optional
|
1530
1530
|
The desired tolerance. The default is 0.0001.
|
1531
|
+
silent : bool , optional
|
1532
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1531
1533
|
|
1532
1534
|
Returns
|
1533
1535
|
-------
|
@@ -1535,22 +1537,53 @@ class Face():
|
|
1535
1537
|
The created vertex.
|
1536
1538
|
|
1537
1539
|
"""
|
1540
|
+
def get_uv_radially():
|
1541
|
+
"""
|
1542
|
+
Generate the points of a grid with a given size n, sorted radially from the center to the periphery.
|
1543
|
+
n should be an odd number, ensuring that there's a center point (0, 0).
|
1544
|
+
|
1545
|
+
Args:
|
1546
|
+
n (int): The size of the grid. It should be odd for a clear center point.
|
1547
|
+
|
1548
|
+
Returns:
|
1549
|
+
list: A list of tuples (x, y) sorted by radial distance from the center (0, 0).
|
1550
|
+
"""
|
1551
|
+
import math
|
1552
|
+
|
1553
|
+
points = []
|
1554
|
+
n = 100
|
1555
|
+
# Iterate over the grid, ranging from -n//2 to n//2
|
1556
|
+
for x in range(-n//2, n//2 + 1):
|
1557
|
+
for y in range(-n//2, n//2 + 1):
|
1558
|
+
points.append((x, y))
|
1559
|
+
|
1560
|
+
# Sort points by their Euclidean distance from the center (0, 0)
|
1561
|
+
points.sort(key=lambda point: math.sqrt(point[0]**2 + point[1]**2))
|
1562
|
+
return_points = []
|
1563
|
+
for p in points:
|
1564
|
+
new_p = ((p[0]+50)*0.01, (p[1]+50)*0.01)
|
1565
|
+
return_points.append(new_p)
|
1566
|
+
return return_points
|
1567
|
+
|
1538
1568
|
from topologicpy.Vertex import Vertex
|
1539
1569
|
from topologicpy.Topology import Topology
|
1540
1570
|
|
1541
1571
|
if not Topology.IsInstance(face, "Face"):
|
1542
1572
|
return None
|
1543
|
-
|
1544
|
-
if Vertex.IsInternal(
|
1545
|
-
return
|
1546
|
-
|
1547
|
-
for
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1573
|
+
vert = Topology.Centroid(face)
|
1574
|
+
if Vertex.IsInternal(vert, face, tolerance=tolerance):
|
1575
|
+
return vert
|
1576
|
+
uv_list = get_uv_radially()
|
1577
|
+
for uv in uv_list:
|
1578
|
+
u, v = uv
|
1579
|
+
vert = Face.VertexByParameters(face, u, v)
|
1580
|
+
if Vertex.IsInternal(vert, face, tolerance=tolerance):
|
1581
|
+
return vert
|
1582
|
+
if not silent:
|
1583
|
+
print("Face.InternalVertex - Warning: Could not find an internal vertex. Returning the first vertex of the face.")
|
1584
|
+
vert = Topology.Vertices(face)[0]
|
1585
|
+
#v = topologic.FaceUtility.InternalVertex(face, tolerance) # Hook to Core
|
1586
|
+
return vert
|
1554
1587
|
|
1555
1588
|
@staticmethod
|
1556
1589
|
def Invert(face, tolerance: float = 0.0001):
|
@@ -1657,6 +1690,8 @@ class Face():
|
|
1657
1690
|
"""
|
1658
1691
|
Returns the face representing the isovist projection from the input viewpoint.
|
1659
1692
|
This method assumes all input is in 2D. Z coordinates are ignored.
|
1693
|
+
This method and the metrics are largely derived from isovists.org. Even if not explicitly listed, please assume that all credit
|
1694
|
+
goes to the authors of that website and its associated software.
|
1660
1695
|
|
1661
1696
|
Parameters
|
1662
1697
|
----------
|
@@ -1698,6 +1733,12 @@ class Face():
|
|
1698
1733
|
- d_f : float, Fractal Dimension measures the complexity of the isovist's boundary.
|
1699
1734
|
- e_c : float , Edge Complexity measures how complex the edges of the isovist boundary are.
|
1700
1735
|
- theta : float, Mean Visual Field Angle measures the average angular extent of the visible area from the observation point.
|
1736
|
+
- occlusivity: float, the proportion of edges of an isovist that are not physically defined.
|
1737
|
+
- drift: float, the distance from the observation point to the centroid of its isovist.
|
1738
|
+
- closed_perimeter: float, the total length of non-occluded edges of the isovist.
|
1739
|
+
- average_radial: float, "the mean view length of all space visible from a location." (from isovists.org)
|
1740
|
+
- variance: float, "the mean of the square of deviation between all radial lengths and average radial length of an isovist (Benedikt, 1979)." (from isovists.org)
|
1741
|
+
- skewness: float, "the mean of the cube of deviation between all radial lengths and average radial length of an isovist (Benediky, 1979)." (from isovists.org)
|
1701
1742
|
triangles : bool , optional
|
1702
1743
|
If set to True, the subtended triangles of the isovist are created and stored as contents of the returned isovist face. The default is False.
|
1703
1744
|
mantissa : int , optional
|
@@ -1901,6 +1942,81 @@ class Face():
|
|
1901
1942
|
|
1902
1943
|
return float(distance), Vertex.ByCoordinates(list(closest_point))
|
1903
1944
|
|
1945
|
+
|
1946
|
+
|
1947
|
+
def compute_average_radial_variance_skewness(vertex, edges, mantissa=6):
|
1948
|
+
from math import atan2, pi, sqrt, pow
|
1949
|
+
|
1950
|
+
def subtended_angle(vertex, edge, mantissa=6):
|
1951
|
+
"""Compute the angle subtended by the edge at point V."""
|
1952
|
+
v = Vertex.Coordinates(vertex, mantissa=mantissa)
|
1953
|
+
start = Vertex.Coordinates(Edge.StartVertex(edge), mantissa=mantissa)
|
1954
|
+
end = Vertex.Coordinates(Edge.EndVertex(edge), mantissa=mantissa)
|
1955
|
+
# Calculate the angles of the start and end vertices relative to V
|
1956
|
+
angle_start = atan2(start[1] - v[1], start[0] - v[0])
|
1957
|
+
angle_end = atan2(end[1] - v[1], end[0] - v[0])
|
1958
|
+
# Ensure the angle is in the range [0, 2*pi]
|
1959
|
+
angle_start = angle_start if angle_start >= 0 else angle_start + 2 * pi
|
1960
|
+
angle_end = angle_end if angle_end >= 0 else angle_end + 2 * pi
|
1961
|
+
# Compute the difference and handle wrapping around 2*pi
|
1962
|
+
angle_diff = abs(angle_end - angle_start)
|
1963
|
+
return min(angle_diff, 2 * pi - angle_diff)
|
1964
|
+
|
1965
|
+
total_weighted_distance = 0
|
1966
|
+
total_angle_weight = 0
|
1967
|
+
total_weighted_squared_deviation = 0
|
1968
|
+
total_weighted_cubed_deviation = 0
|
1969
|
+
distances = []
|
1970
|
+
angles = []
|
1971
|
+
for edge in edges:
|
1972
|
+
# Calculate the distance between V and the edge
|
1973
|
+
distance = Vertex.Distance(vertex, edge, mantissa=mantissa)
|
1974
|
+
distances.append(distance)
|
1975
|
+
|
1976
|
+
# Calculate the subtended angle for the edge
|
1977
|
+
angle = subtended_angle(vertex, edge, mantissa=mantissa)
|
1978
|
+
angles.append(angle)
|
1979
|
+
|
1980
|
+
# Weight the distance by the subtended angle
|
1981
|
+
total_weighted_distance += distance * angle
|
1982
|
+
total_angle_weight += angle
|
1983
|
+
|
1984
|
+
# Compute the Average Radial value
|
1985
|
+
if total_angle_weight == 0:
|
1986
|
+
average_radial = 0 # Avoid division by zero
|
1987
|
+
else:
|
1988
|
+
average_radial = round(total_weighted_distance / total_angle_weight, mantissa)
|
1989
|
+
|
1990
|
+
# Compute Variance
|
1991
|
+
for i, edge in enumerate(edges):
|
1992
|
+
# Calculate the distance between V and the edge
|
1993
|
+
distance = distances[i]
|
1994
|
+
# Calculate the subtended angle for the edge
|
1995
|
+
angle = angles[i]
|
1996
|
+
|
1997
|
+
# Calculate the deviation squared from the average radial
|
1998
|
+
deviation_squared = (distance - average_radial) ** 2
|
1999
|
+
# Calculate the deviation cubed from the average radial
|
2000
|
+
deviation_cubed = (distance - average_radial) ** 3
|
2001
|
+
# Weight the squared deviation by the subtended angle
|
2002
|
+
total_weighted_squared_deviation += deviation_squared * angle
|
2003
|
+
total_weighted_cubed_deviation += deviation_cubed * angle
|
2004
|
+
|
2005
|
+
# Compute the Variance value
|
2006
|
+
if total_angle_weight == 0:
|
2007
|
+
variance = 0 # Avoid division by zero
|
2008
|
+
else:
|
2009
|
+
variance = round(sqrt(total_weighted_squared_deviation / total_angle_weight), mantissa)
|
2010
|
+
|
2011
|
+
# Compute the Skewness value
|
2012
|
+
if total_angle_weight == 0:
|
2013
|
+
skewness = 0 # Avoid division by zero
|
2014
|
+
else:
|
2015
|
+
skewness = round(pow(total_weighted_cubed_deviation / total_angle_weight, 1/3), mantissa)
|
2016
|
+
|
2017
|
+
return average_radial, variance, skewness
|
2018
|
+
|
2019
|
+
# Main Code
|
1904
2020
|
origin = Topology.Centroid(face)
|
1905
2021
|
normal = Face.Normal(face)
|
1906
2022
|
flat_face = Topology.Flatten(face, origin=origin, direction=normal)
|
@@ -1989,7 +2105,7 @@ class Face():
|
|
1989
2105
|
return None
|
1990
2106
|
simpler_face = Face.RemoveCollinearEdges(return_face)
|
1991
2107
|
if Topology.IsInstance(simpler_face, "face"):
|
1992
|
-
if transferDictionaries == True:
|
2108
|
+
if transferDictionaries == True or metrics == True:
|
1993
2109
|
j_edges = [Topology.Edges(t) for t in obstacles]
|
1994
2110
|
j_edges = Helper.Flatten(j_edges)
|
1995
2111
|
j_edges += Topology.Edges(face)
|
@@ -1997,19 +2113,24 @@ class Face():
|
|
1997
2113
|
used = [0 for _ in range(len(j_edges))]
|
1998
2114
|
for i, i_edge in enumerate(i_edges):
|
1999
2115
|
d_i = Topology.Dictionary(i_edge)
|
2116
|
+
d_i = Dictionary.SetValueAtKey(d_i, "occlusive", True)
|
2117
|
+
i_edge = Topology.SetDictionary(i_edge, d_i)
|
2000
2118
|
for j, j_edge in enumerate(j_edges):
|
2001
2119
|
if used[j] == 0:
|
2002
2120
|
if Edge.IsCollinear(i_edge, j_edge):
|
2121
|
+
d_i = Dictionary.SetValueAtKey(d_i, "occlusive", False)
|
2122
|
+
i_edge = Topology.SetDictionary(i_edge, d_i)
|
2003
2123
|
d_j = Topology.Dictionary(j_edge)
|
2004
2124
|
d_result = Dictionary.ByMergedDictionaries([d_i, d_j])
|
2005
|
-
|
2125
|
+
if transferDictionaries == True:
|
2126
|
+
i_edge = Topology.SetDictionary(i_edge, d_result)
|
2006
2127
|
used[j] == 1
|
2007
2128
|
|
2008
2129
|
return_face = Topology.Unflatten(simpler_face, origin=origin, direction=normal)
|
2009
|
-
|
2130
|
+
else:
|
2131
|
+
return_face = Topology.Unflatten(return_face, origin=origin, direction=normal)
|
2010
2132
|
if metrics == True:
|
2011
2133
|
vertices = Topology.Vertices(return_face)
|
2012
|
-
edges = Topology.Edges(return_face)
|
2013
2134
|
# 1 Viewpoint
|
2014
2135
|
viewpoint = Vertex.Coordinates(vertex, mantissa=mantissa)
|
2015
2136
|
# 2 Direction
|
@@ -2077,6 +2198,27 @@ class Face():
|
|
2077
2198
|
e_c = round(edge_complexity(coords), mantissa)
|
2078
2199
|
# 18 Mean Visual Field Angle
|
2079
2200
|
theta = round(mean_visual_field_angle(viewpoint, coords), mantissa)
|
2201
|
+
# 19 Occlusivity
|
2202
|
+
occ_length = 0
|
2203
|
+
edges = Topology.Edges(return_face)
|
2204
|
+
for edge in edges:
|
2205
|
+
d = Topology.Dictionary(edge)
|
2206
|
+
if Dictionary.ValueAtKey(d, "occlusive") == True:
|
2207
|
+
occ_length += Edge.Length(edge)
|
2208
|
+
if perimeter > 0:
|
2209
|
+
occlusivity = round(occ_length/perimeter, mantissa)
|
2210
|
+
else:
|
2211
|
+
occlusivity = round(0.0, 6)
|
2212
|
+
|
2213
|
+
# 20 Drift
|
2214
|
+
drift = Vertex.Distance(vertex, Topology.Centroid(return_face), mantissa=mantissa)
|
2215
|
+
|
2216
|
+
# 21 Closed Perimeter
|
2217
|
+
closed_perimeter = round(perimeter - occ_length, mantissa)
|
2218
|
+
|
2219
|
+
# 22/23/24 Average Radial, Variance, and Skewness
|
2220
|
+
average_radial, variance, skewness = compute_average_radial_variance_skewness(vertex, edges, mantissa=6)
|
2221
|
+
|
2080
2222
|
keys = ["viewpoint",
|
2081
2223
|
"direction",
|
2082
2224
|
"fov",
|
@@ -2094,7 +2236,14 @@ class Face():
|
|
2094
2236
|
"symmetry",
|
2095
2237
|
"d_f",
|
2096
2238
|
"e_c",
|
2097
|
-
"theta"
|
2239
|
+
"theta",
|
2240
|
+
"occlusivity",
|
2241
|
+
"drift",
|
2242
|
+
"closed_perimeter",
|
2243
|
+
"average_radial",
|
2244
|
+
"variance",
|
2245
|
+
"skewness"]
|
2246
|
+
|
2098
2247
|
values = [viewpoint,
|
2099
2248
|
direction,
|
2100
2249
|
fov,
|
@@ -2112,7 +2261,13 @@ class Face():
|
|
2112
2261
|
symmetry,
|
2113
2262
|
d_f,
|
2114
2263
|
e_c,
|
2115
|
-
theta
|
2264
|
+
theta,
|
2265
|
+
occlusivity,
|
2266
|
+
drift,
|
2267
|
+
closed_perimeter,
|
2268
|
+
average_radial,
|
2269
|
+
variance,
|
2270
|
+
skewness]
|
2116
2271
|
d = Dictionary.ByKeysValues(keys, values)
|
2117
2272
|
return_face = Topology.SetDictionary(return_face, d)
|
2118
2273
|
if triangles:
|
@@ -2365,6 +2520,8 @@ class Face():
|
|
2365
2520
|
The desired length of the normal edge. The default is 1.
|
2366
2521
|
tolerance : float , optional
|
2367
2522
|
The desired tolerance. The default is 0.0001.
|
2523
|
+
silent : bool , optional
|
2524
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
2368
2525
|
|
2369
2526
|
Returns
|
2370
2527
|
-------
|