topologicpy 0.7.80__py3-none-any.whl → 0.7.82__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/Face.py CHANGED
@@ -1649,7 +1649,7 @@ class Face():
1649
1649
  return are_planes_coplanar(plane_a, plane_b, tolerance=tolerance)
1650
1650
 
1651
1651
  @staticmethod
1652
- def Isovist(face, vertex, obstacles: list = [], direction: list = [0,1,0], fov: float = 360, mantissa: int = 6, tolerance: float = 0.0001):
1652
+ def Isovist(face, vertex, obstacles: list = [], direction: list = [0,1,0], fov: float = 360, transferDictionaries: bool = False, metrics: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
1653
1653
  """
1654
1654
  Returns the face representing the isovist projection from the input viewpoint.
1655
1655
  This method assumes all input is in 2D. Z coordinates are ignored.
@@ -1671,6 +1671,25 @@ class Face():
1671
1671
  fov : float , optional
1672
1672
  The horizontal field of view (fov) angle in degrees. See https://en.wikipedia.org/wiki/Field_of_view.
1673
1673
  The acceptable range is 1 to 360. The default is 360.
1674
+ transferDictionaries : bool , optional
1675
+ If set to True, the dictionaries of the encountered edges will be transfered to the isovist edges. The default is False.
1676
+ metrics : bool , optional
1677
+ If set to True, the following metrics are calculated and stored in the dictionary of the returned isovist. The keys of the values are:
1678
+ - viewpoint : list , the x , y , z coordinates of the location of the viewpoint.
1679
+ - direction : list , the direction of the view.
1680
+ - fov : int, Field of view angle.
1681
+ - area : float , the area of the isovist.
1682
+ - perimeter : float , the perimeter length of the isovist
1683
+ - compactness : float , how closely the shape of the isovist approximates a circle (the most compact geometric shape).
1684
+ - d_max : float, Maximum Visibility Distance. the length of the longest straight line that can be seen from the viewpoint.
1685
+ - v_max : list, Furthest Point measures the x , y , z coordinates of the furthest visible point from the viewpoint.
1686
+ - v_d : list, Visibility Distribution quantifies the angular distribution (in degrees) of visible points across the isovist.
1687
+ This metric can tell you whether the visibility from a point is more spread out or concentrated in a certain direction. A uniform visibility distribution indicates a more balanced visual field, while a skewed distribution suggests that the observer's line of sight is constrained in certain directions.
1688
+ - v_density : float, Viewpoint Density which refers to the number of visible points per unit area within the isovist.
1689
+ - symmetry : float, Symmetry measures how balanced or symmetrical the isovist is around the point of observation.
1690
+ - d_f : float, Fractal Dimension measures the complexity of the isovist's boundary.
1691
+ - e_c : float , Edge Complexity measures how complex the edges of the isovist boundary are.
1692
+ - theta : float, Mean Visual Field Angle measures the average angular extent of the visible area from the observation point.
1674
1693
  mantissa : int , optional
1675
1694
  The desired length of the mantissa. The default is 6.
1676
1695
  tolerance : float , optional:
@@ -1690,8 +1709,132 @@ class Face():
1690
1709
  from topologicpy.Cluster import Cluster
1691
1710
  from topologicpy.Topology import Topology
1692
1711
  from topologicpy.Vector import Vector
1712
+ from topologicpy.Dictionary import Dictionary
1713
+ from topologicpy.Helper import Helper
1714
+
1715
+ import math
1716
+ import numpy as np
1717
+
1718
+ def calculate_angle(viewpoint, vertex):
1719
+ # Calculate the angle between the viewpoint and the vertex in the 2D plane
1720
+ # Viewpoint is (x, y, z), and vertex is (x, y)
1721
+ dx = vertex[0] - viewpoint[0]
1722
+ dy = vertex[1] - viewpoint[1]
1723
+
1724
+ # Return the angle in radians using the arctangent of the y/x difference
1725
+ return math.degrees(math.atan2(dy, dx))
1726
+
1727
+ def visibility_distribution(viewpoint, isovist_vertices):
1728
+ angles = []
1729
+ # Calculate the angle of each vertex with respect to the viewpoint
1730
+ for vertex in isovist_vertices:
1731
+ angle = calculate_angle(viewpoint, vertex)
1732
+ angles.append(angle)
1733
+ # Sort the angles to analyze the distribution
1734
+ angles = np.sort(angles)
1735
+ return list(angles)
1736
+
1737
+ def isovist_symmetry(viewpoint, isovist_vertices):
1738
+ """
1739
+ Calculates the symmetry of the isovist polygon.
1740
+
1741
+ Parameters:
1742
+ - viewpoint: a tuple (x, y) of the viewpoint.
1743
+ - isovist_vertices: a list of tuples, each representing a vertex (x, y) of the isovist polygon.
1744
+
1745
+ Returns:
1746
+ - symmetry value: A measure of the symmetry of the isovist.
1747
+ """
1748
+ angles = [calculate_angle(viewpoint, vertex) for vertex in isovist_vertices]
1749
+ angles.sort()
1750
+
1751
+ # Calculate angular deviations from the mean direction
1752
+ mean_angle = np.mean(angles)
1753
+ angular_deviation = np.std(angles)
1754
+ symmetry = angular_deviation / mean_angle if mean_angle != 0 else 0
1755
+ return float(symmetry)
1756
+
1757
+ # Fractal Dimension (D_f) using Box-counting
1758
+ def fractal_dimension(isovist_vertices):
1759
+ """
1760
+ Calculates the fractal dimension of the isovist boundary using box-counting.
1761
+
1762
+ Parameters:
1763
+ - isovist_vertices: a list of tuples, each representing a vertex (x, y) of the isovist polygon.
1764
+
1765
+ Returns:
1766
+ - fractal dimension: A measure of the boundary's complexity.
1767
+ """
1768
+ # Convert isovist vertices into a boundary path (x, y coordinates)
1769
+ boundary_points = np.array(isovist_vertices)
1693
1770
 
1694
- def vertexPartofFace(vertex, face, tolerance):
1771
+ # Box-counting approach
1772
+ sizes = np.logspace(0, 2, 10) # Varying box sizes (log scale)
1773
+ sizes[sizes == 0] = 1e-10 # Replace zero counts with a small value
1774
+ counts = []
1775
+
1776
+ for size in sizes:
1777
+ count = 0
1778
+ for i in range(len(boundary_points)):
1779
+ if np.abs(boundary_points[i][0] - boundary_points[(i+1)%len(boundary_points)][0]) > size or \
1780
+ np.abs(boundary_points[i][1] - boundary_points[(i+1)%len(boundary_points)][1]) > size:
1781
+ count += 1
1782
+ counts.append(count)
1783
+
1784
+ # To avoid log(0), add a small constant to counts
1785
+ counts = np.array(counts)
1786
+ counts = np.where(counts == 0, 1e-10, counts)
1787
+ # Linear regression of log(count) vs log(size) to estimate fractal dimension
1788
+ log_sizes = np.log(sizes)
1789
+ log_counts = np.log(counts)
1790
+
1791
+ # Perform linear regression (log-log scale)
1792
+ slope, _ = np.polyfit(log_sizes, log_counts, 1)
1793
+
1794
+ return slope
1795
+
1796
+ # Edge Complexity (E_C)
1797
+ def edge_complexity(isovist_vertices):
1798
+ """
1799
+ Calculates the edge complexity of the isovist boundary.
1800
+
1801
+ Parameters:
1802
+ - isovist_vertices: a list of tuples, each representing a vertex (x, y) of the isovist polygon.
1803
+
1804
+ Returns:
1805
+ - edge complexity: A measure of the complexity of the boundary.
1806
+ """
1807
+ angles = []
1808
+ for i in range(len(isovist_vertices)):
1809
+ p1 = isovist_vertices[i]
1810
+ p2 = isovist_vertices[(i + 1) % len(isovist_vertices)]
1811
+ p3 = isovist_vertices[(i + 2) % len(isovist_vertices)]
1812
+
1813
+ # Calculate the angle between each consecutive edge
1814
+ angle = np.arctan2(p3[1] - p2[1], p3[0] - p2[0]) - np.arctan2(p1[1] - p2[1], p1[0] - p2[0])
1815
+ angles.append(np.abs(angle))
1816
+
1817
+ # Complexity is the number of abrupt angle changes
1818
+ complexity = np.sum(np.array(angles) > np.pi / 4) # e.g., large changes in angles
1819
+ return float(complexity)
1820
+
1821
+ # Mean Visual Field Angle (θ)
1822
+ def mean_visual_field_angle(viewpoint, isovist_vertices):
1823
+ """
1824
+ Calculates the mean visual field angle from the viewpoint to the isovist vertices.
1825
+
1826
+ Parameters:
1827
+ - viewpoint: a tuple (x, y) of the viewpoint.
1828
+ - isovist_vertices: a list of tuples, each representing a vertex (x, y) of the isovist polygon.
1829
+
1830
+ Returns:
1831
+ - mean visual field angle in degrees.
1832
+ """
1833
+ angles = [calculate_angle(viewpoint, vertex) for vertex in isovist_vertices]
1834
+ # Return the average angle
1835
+ return np.mean(angles)
1836
+
1837
+ def vertex_part_of_face(vertex, face, tolerance):
1695
1838
  vertices = Topology.Vertices(face)
1696
1839
  for v in vertices:
1697
1840
  if Vertex.Distance(vertex, v) < tolerance:
@@ -1716,6 +1859,7 @@ class Face():
1716
1859
  normal = Face.Normal(face)
1717
1860
  flat_face = Topology.Flatten(face, origin=origin, direction=normal)
1718
1861
  flat_vertex = Topology.Flatten(vertex, origin=origin, direction=normal)
1862
+ flat_obstacles = [Topology.Flatten(obstacle, origin=origin, direction=normal) for obstacle in obstacles]
1719
1863
 
1720
1864
  eb = Face.ExternalBoundary(flat_face)
1721
1865
  vertices = Topology.Vertices(eb)
@@ -1732,7 +1876,7 @@ class Face():
1732
1876
  new_ib_list.append(Wire.ByVertices(new_vertices, close=True))
1733
1877
 
1734
1878
  flat_face = Face.ByWires(eb, new_ib_list)
1735
- for obs in obstacles:
1879
+ for obs in flat_obstacles:
1736
1880
  flat_face = Topology.Difference(flat_face, Face.ByWire(obs))
1737
1881
 
1738
1882
  targets = Topology.Vertices(flat_face)
@@ -1751,7 +1895,7 @@ class Face():
1751
1895
  faces = Topology.Faces(shell)
1752
1896
  final_faces = []
1753
1897
  for f in faces:
1754
- if vertexPartofFace(flat_vertex, f, tolerance=0.001):
1898
+ if vertex_part_of_face(flat_vertex, f, tolerance=0.001):
1755
1899
  final_faces.append(f)
1756
1900
  shell = Shell.ByFaces(final_faces)
1757
1901
  return_face = Topology.RemoveCoplanarFaces(shell, epsilon=0.1)
@@ -1795,8 +1939,94 @@ class Face():
1795
1939
  return None
1796
1940
  simpler_face = Face.RemoveCollinearEdges(return_face)
1797
1941
  if Topology.IsInstance(simpler_face, "face"):
1798
- return Topology.Unflatten(simpler_face, origin=origin, direction=normal)
1799
- return Topology.Unflatten(return_face, origin=origin, direction=normal)
1942
+ if transferDictionaries == True:
1943
+ j_edges = [Topology.Edges(t) for t in obstacles]
1944
+ j_edges = Helper.Flatten(j_edges)
1945
+ j_edges += Topology.Edges(face)
1946
+ i_edges = Topology.Edges(simpler_face)
1947
+ used = [0 for _ in range(len(j_edges))]
1948
+ for i, i_edge in enumerate(i_edges):
1949
+ d_i = Topology.Dictionary(i_edge)
1950
+ for j, j_edge in enumerate(j_edges):
1951
+ if used[j] == 0:
1952
+ if Edge.IsCollinear(i_edge, j_edge):
1953
+ d_j = Topology.Dictionary(j_edge)
1954
+ d_result = Dictionary.ByMergedDictionaries([d_i, d_j])
1955
+ i_edge = Topology.SetDictionary(i_edge, d_result)
1956
+ used[j] == 1
1957
+
1958
+ return_face = Topology.Unflatten(simpler_face, origin=origin, direction=normal)
1959
+ return_face = Topology.Unflatten(return_face, origin=origin, direction=normal)
1960
+ if metrics == True:
1961
+ # 1 Viewpoint
1962
+ viewpoint = Vertex.Coordinates(vertex, mantissa=mantissa)
1963
+ # 2 Direction
1964
+ # direction is given
1965
+ # 3 Field of View (FOV)
1966
+ # fov is given
1967
+ # 4 Area
1968
+ area = round(abs(Face.Area(return_face)), mantissa)
1969
+ # 5 Perimeter
1970
+ perimeter = round(Wire.Length(Face.Wires(return_face)[0]), mantissa)
1971
+ # 6 Compactness
1972
+ compactness = round(Face.Compactness(return_face), mantissa)
1973
+ # 7 Maximum Distance (d_max)
1974
+ vertices = Topology.Vertices(return_face)
1975
+ furthest_vertex = vertices[0]
1976
+ d_max = round(Vertex.Distance(vertex, vertices[0]), mantissa)
1977
+ # 8 Furthest Visible Vertex
1978
+ coords = []
1979
+ for v in vertices:
1980
+ coords.append(Vertex.Coordinates(v, mantissa=mantissa))
1981
+ dis = Vertex.Distance(vertex, v, mantissa=mantissa)
1982
+ if dis > d_max:
1983
+ d_max = dis
1984
+ furthest_vertex = v
1985
+ v_max = Vertex.Coordinates(furthest_vertex, mantissa=mantissa)
1986
+ # 9 Visibility Distribution (v_d)
1987
+ v_d = visibility_distribution(viewpoint, coords)
1988
+ v_d = [round(x) for x in v_d]
1989
+ # 10 Viewpoint density
1990
+ v_density = round(float(len(vertices)) / abs(Face.Area(return_face)), mantissa)
1991
+ # 11 Isovist Symmetry
1992
+ symmetry = round(isovist_symmetry(viewpoint, coords), mantissa)
1993
+ # 12 Fractal Dimension
1994
+ d_f = round(fractal_dimension(coords), mantissa)
1995
+ # 13 Edge Complexity
1996
+ e_c = round(edge_complexity(coords), mantissa)
1997
+ # 14 Mean Visual Field Angle
1998
+ theta = round(mean_visual_field_angle(viewpoint, coords), mantissa)
1999
+ keys = ["viewpoint",
2000
+ "direction",
2001
+ "fov",
2002
+ "area",
2003
+ "perimeter",
2004
+ "compactness",
2005
+ "d_max",
2006
+ "v_max",
2007
+ "v_d",
2008
+ "v_density",
2009
+ "symmetry",
2010
+ "d_f",
2011
+ "e_c",
2012
+ "theta"]
2013
+ values = [viewpoint,
2014
+ direction,
2015
+ fov,
2016
+ area,
2017
+ perimeter,
2018
+ compactness,
2019
+ d_max,
2020
+ v_max,
2021
+ v_d,
2022
+ v_density,
2023
+ symmetry,
2024
+ d_f,
2025
+ e_c,
2026
+ theta]
2027
+ d = Dictionary.ByKeysValues(keys, values)
2028
+ return_face = Topology.SetDictionary(return_face, d)
2029
+ return return_face
1800
2030
 
1801
2031
  @staticmethod
1802
2032
  def MedialAxis(face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001):
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.7.80'
1
+ __version__ = '0.7.82'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: topologicpy
3
- Version: 0.7.80
3
+ Version: 0.7.82
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
@@ -10,7 +10,7 @@ topologicpy/DGL.py,sha256=Dd6O08D-vSxpjHYgKm45JpKiaeGvWlg1BRMzYMAXGNc,138991
10
10
  topologicpy/Dictionary.py,sha256=0AsGoz48pGTye_F4KcJopNjD9STeQ50LHc6PPvERFaA,31932
11
11
  topologicpy/Edge.py,sha256=9u9SdUxuenLUIK26xwFvPoYV34p0dCfXmHHBxdgvAdM,67164
12
12
  topologicpy/EnergyModel.py,sha256=AqTtmXE35SxvRXhG3vYAQd7GQDW-6HtjYPHua6ME4Eg,53762
13
- topologicpy/Face.py,sha256=mS7LI3qIo2jfQbibpInArRfXMmTZd7xU8IdvRYUIGSU,124747
13
+ topologicpy/Face.py,sha256=Y13hw84bjJO8bzEG7sB7PRDxcSqqmGZnNAbIhIUQ1Ys,136243
14
14
  topologicpy/Graph.py,sha256=ZhMVB6ntuhIgTrKLUPryeAJFFBF0cnRsNgESbaohOiw,416914
15
15
  topologicpy/Grid.py,sha256=9N6PE84qCm40TRi2WtlVZSBwXXr47zHpscEpZHg_JW4,18205
16
16
  topologicpy/Helper.py,sha256=vUWbnZJxn0e9YsFUNyJ5Dee8Nh6TmDp9uCzCk4e0qAA,21897
@@ -28,9 +28,9 @@ topologicpy/Vector.py,sha256=A1g83zDHep58iVPY8WQ8iHNrSOfGWFEzvVeDuMnjDNY,33078
28
28
  topologicpy/Vertex.py,sha256=mcLJaWFCct03dkVmT25wAl6k0k2EaYvB1PWNO9WQHWg,73465
29
29
  topologicpy/Wire.py,sha256=o6MlOjubkJ_f5s1tUamizCsoPKvcQ2OudtJv0yCF-2o,186477
30
30
  topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
31
- topologicpy/version.py,sha256=aBUXqj0fVv7Q8TFJaUl3kuETKLosGV1MVLaSEclOujg,23
32
- topologicpy-0.7.80.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
- topologicpy-0.7.80.dist-info/METADATA,sha256=g22rsF0lEieZmVNOo3LukQGLjnAykoocyDmw2qBPHwg,10513
34
- topologicpy-0.7.80.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
35
- topologicpy-0.7.80.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
- topologicpy-0.7.80.dist-info/RECORD,,
31
+ topologicpy/version.py,sha256=rXEi2ZZcGFgf2u3AMH7tyJUFE9nvS8L279fdB54xeCk,23
32
+ topologicpy-0.7.82.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
+ topologicpy-0.7.82.dist-info/METADATA,sha256=CZZqWePZAWM-hGdFkhmjQf9qbm3Ub_fKCPzHnu7RYvk,10513
34
+ topologicpy-0.7.82.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
35
+ topologicpy-0.7.82.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
+ topologicpy-0.7.82.dist-info/RECORD,,