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 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
- print("Cell.InternalVertex - Error: The input cell parameter is not a valid topologic cell. Returning None.")
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
- print("Cell.InternalVertex - Error: Could not create an internal vertex. Returning None.")
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
- v = Topology.Centroid(face)
1544
- if Vertex.IsInternal(v, face, tolerance=tolerance):
1545
- return v
1546
- l = [0.4,0.6,0.3,0.7,0.2,0.8,0.1,0.9]
1547
- for u in l:
1548
- for v in l:
1549
- v = Face.VertexByParameters(face, u, v)
1550
- if Vertex.IsInternal(v, face, tolerance=tolerance):
1551
- return v
1552
- v = topologic.FaceUtility.InternalVertex(face, tolerance) # Hook to Core
1553
- return v
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
- i_edge = Topology.SetDictionary(i_edge, d_result)
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
- return_face = Topology.Unflatten(return_face, origin=origin, direction=normal)
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
  -------