topologicpy 0.8.31__py3-none-any.whl → 0.8.35__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/Shell.py CHANGED
@@ -739,42 +739,6 @@ class Shell():
739
739
  _ = shell.Faces(None, faces)
740
740
  return faces
741
741
 
742
- @staticmethod
743
- def IsOnBoundary(shell, vertex, tolerance: float = 0.0001) -> bool:
744
- """
745
- Returns True if the input vertex is on the boundary of the input shell. Returns False otherwise. On the boundary is defined as being on the boundary of one of the shell's external or internal boundaries
746
-
747
- Parameters
748
- ----------
749
- shell : topologic_core.Shell
750
- The input shell.
751
- vertex : topologic_core.Vertex
752
- The input vertex.
753
- tolerance : float , optional
754
- The desired tolerance. The default is 0.0001.
755
-
756
- Returns
757
- -------
758
- bool
759
- Returns True if the input vertex is inside the input shell. Returns False otherwise.
760
-
761
- """
762
- from topologicpy.Vertex import Vertex
763
- from topologicpy.Topology import Topology
764
-
765
- if not Topology.IsInstance(shell, "Shell"):
766
- return None
767
- if not Topology.IsInstance(vertex, "Vertex"):
768
- return None
769
- boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
770
- if Vertex.IsInternal(vertex, boundary, tolerance=tolerance):
771
- return True
772
- internal_boundaries = Shell.InternalBoundaries(shell, tolerance=tolerance)
773
- for ib in internal_boundaries:
774
- if Vertex.IsInternal(vertex, ib, tolerance=tolerance):
775
- return True
776
- return False
777
-
778
742
  @staticmethod
779
743
  def HyperbolicParaboloidRectangularDomain(origin= None,
780
744
  llVertex= None,
@@ -1097,6 +1061,41 @@ class Shell():
1097
1061
  """
1098
1062
  return shell.IsClosed()
1099
1063
 
1064
+ @staticmethod
1065
+ def IsOnBoundary(shell, vertex, tolerance: float = 0.0001) -> bool:
1066
+ """
1067
+ Returns True if the input vertex is on the boundary of the input shell. Returns False otherwise. On the boundary is defined as being on the boundary of one of the shell's external or internal boundaries
1068
+
1069
+ Parameters
1070
+ ----------
1071
+ shell : topologic_core.Shell
1072
+ The input shell.
1073
+ vertex : topologic_core.Vertex
1074
+ The input vertex.
1075
+ tolerance : float , optional
1076
+ The desired tolerance. The default is 0.0001.
1077
+
1078
+ Returns
1079
+ -------
1080
+ bool
1081
+ Returns True if the input vertex is inside the input shell. Returns False otherwise.
1082
+
1083
+ """
1084
+ from topologicpy.Vertex import Vertex
1085
+ from topologicpy.Topology import Topology
1086
+
1087
+ if not Topology.IsInstance(shell, "Shell"):
1088
+ return None
1089
+ if not Topology.IsInstance(vertex, "Vertex"):
1090
+ return None
1091
+ boundary = Shell.ExternalBoundary(shell, tolerance=tolerance)
1092
+ if Vertex.IsInternal(vertex, boundary, tolerance=tolerance):
1093
+ return True
1094
+ internal_boundaries = Shell.InternalBoundaries(shell, tolerance=tolerance)
1095
+ for ib in internal_boundaries:
1096
+ if Vertex.IsInternal(vertex, ib, tolerance=tolerance):
1097
+ return True
1098
+ return False
1100
1099
 
1101
1100
  @staticmethod
1102
1101
  def Paraboloid(origin= None, focalLength=0.125, width: float = 1, length: float = 1, uSides: int = 16, vSides: int = 16,
@@ -1644,62 +1643,6 @@ class Shell():
1644
1643
  else:
1645
1644
  return None
1646
1645
 
1647
- def Skeleton(face, tolerance: float = 0.001):
1648
- """
1649
- Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
1650
- This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
1651
-
1652
- Parameters
1653
- ----------
1654
- face : topologic_core.Face
1655
- The input face.
1656
- tolerance : float , optional
1657
- The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
1658
-
1659
- Returns
1660
- -------
1661
- topologic_core.Shell
1662
- The created straight skeleton.
1663
-
1664
- """
1665
- from topologicpy.Wire import Wire
1666
- from topologicpy.Face import Face
1667
- from topologicpy.Topology import Topology
1668
- import topologic_core as topologic
1669
- import math
1670
-
1671
- if not Topology.IsInstance(face, "Face"):
1672
- return None
1673
- roof = Wire.Skeleton(face, tolerance=tolerance)
1674
- if not (Topology.IsInstance(roof, "Wire") or Topology.IsInstance(roof, "Cluster")):
1675
- print("Shell.Skeleton - Error: Could not create base skeleton wire. Returning None.")
1676
- return None
1677
- br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
1678
- if not Topology.IsInstance(br, "Wire"):
1679
- print("Shell.Skeleton - Error: Could not create a bounding rectangle wire. Returning None.")
1680
- return None
1681
- br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
1682
- bf = Face.ByWire(br, tolerance=tolerance)
1683
- if not Topology.IsInstance(bf, "Face"):
1684
- print("Shell.Skeleton - Error: Could not create a bounding rectangle face. Returning None.")
1685
- return None
1686
- large_shell = Topology.Boolean(bf, roof, operation="slice", tolerance=tolerance)
1687
- if not large_shell:
1688
- return None
1689
- faces = Topology.Faces(large_shell)
1690
- if not faces:
1691
- return None
1692
- final_faces = []
1693
- for f in faces:
1694
- internalBoundaries = Face.InternalBoundaries(f)
1695
- if len(internalBoundaries) == 0:
1696
- final_faces.append(f)
1697
- shell = Shell.ByFaces(final_faces, tolerance=tolerance)
1698
- if not Topology.IsInstance(shell, "Shell"):
1699
- print("Shell.Skeleton - Error: Could not create shell. Returning None.")
1700
- return None
1701
- return shell
1702
-
1703
1646
  @staticmethod
1704
1647
  def Simplify(shell, simplifyBoundary: bool = True, mantissa: int = 6, tolerance: float = 0.0001):
1705
1648
  """
@@ -1846,6 +1789,63 @@ class Shell():
1846
1789
  final_result = Shell.ByFaces(final_faces, tolerance=tolerance)
1847
1790
  return final_result
1848
1791
 
1792
+ @staticmethod
1793
+ def Skeleton(face, tolerance: float = 0.001):
1794
+ """
1795
+ Creates a shell through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
1796
+ This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
1797
+
1798
+ Parameters
1799
+ ----------
1800
+ face : topologic_core.Face
1801
+ The input face.
1802
+ tolerance : float , optional
1803
+ The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
1804
+
1805
+ Returns
1806
+ -------
1807
+ topologic_core.Shell
1808
+ The created straight skeleton.
1809
+
1810
+ """
1811
+ from topologicpy.Wire import Wire
1812
+ from topologicpy.Face import Face
1813
+ from topologicpy.Topology import Topology
1814
+ import topologic_core as topologic
1815
+ import math
1816
+
1817
+ if not Topology.IsInstance(face, "Face"):
1818
+ return None
1819
+ roof = Wire.Skeleton(face, tolerance=tolerance)
1820
+ if not (Topology.IsInstance(roof, "Wire") or Topology.IsInstance(roof, "Cluster")):
1821
+ print("Shell.Skeleton - Error: Could not create base skeleton wire. Returning None.")
1822
+ return None
1823
+ br = Wire.BoundingRectangle(roof) #This works even if it is a Cluster not a Wire
1824
+ if not Topology.IsInstance(br, "Wire"):
1825
+ print("Shell.Skeleton - Error: Could not create a bounding rectangle wire. Returning None.")
1826
+ return None
1827
+ br = Topology.Scale(br, Topology.Centroid(br), 1.5, 1.5, 1)
1828
+ bf = Face.ByWire(br, tolerance=tolerance)
1829
+ if not Topology.IsInstance(bf, "Face"):
1830
+ print("Shell.Skeleton - Error: Could not create a bounding rectangle face. Returning None.")
1831
+ return None
1832
+ large_shell = Topology.Boolean(bf, roof, operation="slice", tolerance=tolerance)
1833
+ if not large_shell:
1834
+ return None
1835
+ faces = Topology.Faces(large_shell)
1836
+ if not faces:
1837
+ return None
1838
+ final_faces = []
1839
+ for f in faces:
1840
+ internalBoundaries = Face.InternalBoundaries(f)
1841
+ if len(internalBoundaries) == 0:
1842
+ final_faces.append(f)
1843
+ shell = Shell.ByFaces(final_faces, tolerance=tolerance)
1844
+ if not Topology.IsInstance(shell, "Shell"):
1845
+ print("Shell.Skeleton - Error: Could not create shell. Returning None.")
1846
+ return None
1847
+ return shell
1848
+
1849
1849
  @staticmethod
1850
1850
  def Square(origin= None, size: float = 1.0,
1851
1851
  uSides: int = 2, vSides: int = 2, direction: list = [0, 0, 1],
@@ -1882,6 +1882,33 @@ class Shell():
1882
1882
  uSides=uSides, vSides=vSides, direction=direction,
1883
1883
  placement=placement, tolerance=tolerance)
1884
1884
 
1885
+ @staticmethod
1886
+ def _grow_connected_group(seed_idx, group_size, adjacency, visited_global):
1887
+ """
1888
+ Attempts to grow a group of the given size starting from seed_idx using adjacency.
1889
+ Returns a list of indices if successful, else None.
1890
+ """
1891
+ from collections import deque
1892
+ import random
1893
+
1894
+ group = [seed_idx]
1895
+ visited = set(group)
1896
+ queue = deque([seed_idx])
1897
+
1898
+ while queue and len(group) < group_size:
1899
+ current = queue.popleft()
1900
+ neighbors = adjacency.get(current, [])
1901
+ random.shuffle(neighbors)
1902
+ for neighbor in neighbors:
1903
+ if neighbor not in visited and neighbor not in visited_global:
1904
+ group.append(neighbor)
1905
+ visited.add(neighbor)
1906
+ queue.append(neighbor)
1907
+ if len(group) >= group_size:
1908
+ break
1909
+
1910
+ return group if len(group) == group_size else None
1911
+
1885
1912
  @staticmethod
1886
1913
  def Vertices(shell) -> list:
1887
1914
  """
topologicpy/Topology.py CHANGED
@@ -7897,13 +7897,6 @@ class Topology():
7897
7897
  calframe = inspect.getouterframes(curframe, 2)
7898
7898
  print('caller name:', calframe[1][3])
7899
7899
  return topology
7900
- if len(dictionary.Keys()) < 1:
7901
- if not silent:
7902
- print("Topology.SetDictionary - Warning: the input dictionary parameter is empty. Returning original input.")
7903
- curframe = inspect.currentframe()
7904
- calframe = inspect.getouterframes(curframe, 2)
7905
- print('caller name:', calframe[1][3])
7906
- return topology
7907
7900
  _ = topology.SetDictionary(dictionary)
7908
7901
  return topology
7909
7902
 
@@ -8861,6 +8854,177 @@ class Topology():
8861
8854
  pass
8862
8855
  return returnTopology
8863
8856
 
8857
+ @staticmethod
8858
+ def SubCombinations(topology,
8859
+ subTopologyType=None,
8860
+ minSize: int = 2,
8861
+ maxSize: int = None,
8862
+ maxCombinations: int = 100,
8863
+ timeLimit: int = 10,
8864
+ removeCoplanarFaces: bool = False,
8865
+ removeCollinearEdges: bool = False,
8866
+ tolerance: float = 0.001,
8867
+ silent: bool = False):
8868
+ """
8869
+ Creates connected sub-combination topologies of the input topology. Warning: This is prone to combinatorial explosion.
8870
+
8871
+ Parameters
8872
+ ----------
8873
+ topology : topologic_core.Topology
8874
+ The input topology. This cannot be vertex or edge.
8875
+ subTopologyType : str , optional except when topology is a Cluster
8876
+ The type of subTopology to include in the combinations.
8877
+ If the input is a Cluster, you must specify the subTopologyType.
8878
+ The options are (case insensitive):
8879
+ - "CellComplex"
8880
+ - "Shell"
8881
+ - "Wire"
8882
+ If set to None, it will be set according to the input topology type as follows:
8883
+ - "Cluster" -> User-defined
8884
+ - "CellComplex" -> "CellComplexes"
8885
+ - "Cell" -> "Shells"
8886
+ - "Shell" -> "Shells"
8887
+ - "Face" -> "Wires"
8888
+ - "Wire" -> "Wires"
8889
+ - "Edge" -> None
8890
+ - "Vertex" -> None
8891
+ - "Graph" -> "Graphs"
8892
+ minSize : int , optional
8893
+ The minimum number of subtopologies to include in a combination. This number cannot be less than 2. The default is 2.
8894
+ maxSize : int , optional
8895
+ The maximum number of faces to include in a combinations. This number cannot be less than the number of subtopologies minus 1. The default is None which means the maximum will be set to the number of subtopologies minus 1.
8896
+ maxCombinations : int , optional
8897
+ The maximum number of combinations to create. The default is 100.
8898
+ timeLimit : int , optional
8899
+ The time limit in seconds. The default is 10 seconds. Note that this time limit only applies to creating the combination indices and not the actual Shells.
8900
+ removeCoplanarFaces : bool , optional
8901
+ If set to True, coplanar faces are removed. Otherwise they are not. The default is False.
8902
+ removeCollinearEdges : bool , optional
8903
+ If set to True, collinear edges are removed. Otherwise they are not. The default is False.
8904
+ tolerance : float , optional
8905
+ The desired tolerance. The default is 0.0001.
8906
+ silent : bool , optional
8907
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
8908
+
8909
+ Returns
8910
+ -------
8911
+ list
8912
+ The list of created sub-combinations.
8913
+
8914
+ """
8915
+ from topologicpy.Cluster import Cluster
8916
+ from topologicpy.Graph import Graph
8917
+ from topologicpy.Topology import Topology
8918
+ from topologicpy.Dictionary import Dictionary
8919
+ from topologicpy.Helper import Helper
8920
+ import random
8921
+ import time
8922
+
8923
+ if Topology.IsInstance(topology, "cluster") and subTopologyType is None:
8924
+ if not silent:
8925
+ print("Topology.SubCombinations - Error: The topology input parameter is a cluster but the subTopologyType is not specified. Returning None.")
8926
+ return None
8927
+
8928
+ if Topology.IsInstance(topology, "vertex") or Topology.IsInstance(topology, "edge"):
8929
+ if not silent:
8930
+ print("Topology.SubCombinations - Error: The topology input parameter cannot be a vertex or an edge. Returning None.")
8931
+ return None
8932
+
8933
+ mapping = {
8934
+ "cellcomplex": "cell",
8935
+ "cell": "face",
8936
+ "shell": "face",
8937
+ "face": "edge",
8938
+ "wire": "edge",
8939
+ "graph": "vertex"
8940
+ }
8941
+
8942
+ type_string = Topology.TypeAsString(topology).lower()
8943
+ if type_string not in mapping:
8944
+ if not silent:
8945
+ print(f"Topology.SubCombinations - Error: The input topology type ({type_string}) is not supported. Returning None.")
8946
+ return None
8947
+
8948
+ if subTopologyType is None:
8949
+ subTopologyType = mapping[type_string]
8950
+ subTopologyType = subTopologyType.lower()
8951
+
8952
+ if subTopologyType not in ["cell", "face", "edge", "vertex"]:
8953
+ if not silent:
8954
+ print(f"Topology.SubCombinations - Error: Unknown subTopologyType: {subTopologyType}. Returning None.")
8955
+ return None
8956
+
8957
+ if ((type_string in ["cell", "shell"]) and subTopologyType not in ["face", "edge"]) or \
8958
+ ((type_string in ["wire", "face"]) and subTopologyType != "edge"):
8959
+ if not silent:
8960
+ print(f"Topology.SubCombinations - Error: The subTopology type {subTopologyType} is not appropriate for topology of type {type_string}. Returning None.")
8961
+ return None
8962
+
8963
+ all_subtopologies = Topology.SubTopologies(topology, subTopologyType=subTopologyType, silent=silent)
8964
+ num_subtopologies = len(all_subtopologies)
8965
+ if num_subtopologies < 2:
8966
+ if not silent:
8967
+ print("Topology.SubCombinations - Warning: Not enough subtopologies found. Returning empty list.")
8968
+ return []
8969
+
8970
+ indices = list(range(num_subtopologies))
8971
+ minSize = max(2, minSize)
8972
+ maxSize = num_subtopologies - 1 if maxSize is None else min(maxSize, num_subtopologies - 1)
8973
+
8974
+ subt_label_key = "__index__"
8975
+ all_subtopologies = [Topology.SetDictionary(subt, Dictionary.ByKeyValue(subt_label_key, i)) for i, subt in enumerate(all_subtopologies)]
8976
+
8977
+ group_sizes = list(range(minSize, maxSize + 1))
8978
+ num_sizes = len(group_sizes)
8979
+ per_size_combinations = maxCombinations // num_sizes
8980
+ per_size_time = timeLimit / num_sizes
8981
+
8982
+ adjacency = Dictionary.AdjacencyDictionary(topology, subTopologyType=subTopologyType, labelKey=subt_label_key, weightKey=None, includeWeights=False)
8983
+
8984
+ results = []
8985
+ seen_groups = set()
8986
+
8987
+ for group_size in reversed(group_sizes):
8988
+ size_start_time = time.time()
8989
+ remaining = per_size_combinations
8990
+ tries = 0
8991
+ max_tries = per_size_combinations * 10
8992
+
8993
+ while remaining > 0 and (time.time() - size_start_time) < per_size_time and tries < max_tries:
8994
+ random.shuffle(indices)
8995
+ for seed in indices:
8996
+ if (time.time() - size_start_time) > per_size_time:
8997
+ break
8998
+ group = Helper.Grow(seed, group_size, adjacency, visited_global=seen_groups)
8999
+ if group:
9000
+ key = tuple(sorted(group))
9001
+ if key not in seen_groups:
9002
+ tries += 1
9003
+ seen_groups.add(key)
9004
+ results.append(group)
9005
+ remaining -= 1
9006
+ if remaining <= 0 or tries >= max_tries:
9007
+ break
9008
+
9009
+ return_combinations = []
9010
+ for result in reversed(results):
9011
+ subtopologies = [all_subtopologies[i] for i in result]
9012
+ # Special case for graphs
9013
+ if Topology.IsInstance(topology, "Graph"):
9014
+ edges = Graph.Edges(topology, vertices = subtopologies)
9015
+ combination = Graph.ByVerticesEdges(subtopologies, edges)
9016
+ else:
9017
+ combination = Topology.SelfMerge(Cluster.ByTopologies(subtopologies), tolerance=tolerance)
9018
+ if removeCollinearEdges:
9019
+ if Topology.IsInstance(combination, "face") or Topology.IsInstance(combination, "wire"):
9020
+ combination = Topology.RemoveCollinearEdges(combination, tolerance=tolerance)
9021
+ if removeCoplanarFaces:
9022
+ if Topology.IsInstance(combination, "cellcomplex") or Topology.IsInstance(combination, "cell") or Topology.IsInstance(combination, "shell"):
9023
+ combination = Topology.RemoveCoplanarFaces(combination, tolerance=tolerance)
9024
+ return_combinations.append(combination)
9025
+
9026
+ return return_combinations
9027
+
8864
9028
  @staticmethod
8865
9029
  def Taper(topology, origin=None, ratioRange: list = [0, 1], triangulate: bool = False, mantissa: int = 6, tolerance: float = 0.0001):
8866
9030
  """
@@ -9188,7 +9352,7 @@ class Topology():
9188
9352
  return Topology.SubTopologies(topology=topology, subTopologyType="cluster")
9189
9353
 
9190
9354
  @staticmethod
9191
- def SubTopologies(topology, subTopologyType="vertex"):
9355
+ def SubTopologies(topology, subTopologyType="vertex", silent: bool = False):
9192
9356
  """
9193
9357
  Returns the subtopologies of the input topology as specified by the subTopologyType input string.
9194
9358
 
@@ -9198,7 +9362,8 @@ class Topology():
9198
9362
  The input topology.
9199
9363
  subTopologyType : str , optional
9200
9364
  The requested subtopology type. This can be one of "vertex", "edge", "wire", "face", "shell", "cell", "cellcomplex", "cluster". It is case insensitive. The default is "vertex".
9201
-
9365
+ silent : bool , optional
9366
+ If set to True, no warnings or errors will be printed. The default is False.
9202
9367
  Returns
9203
9368
  -------
9204
9369
  list
@@ -9206,15 +9371,28 @@ class Topology():
9206
9371
 
9207
9372
  """
9208
9373
  from topologicpy.Face import Face
9374
+ from topologicpy.Graph import Graph
9209
9375
 
9210
- if not Topology.IsInstance(topology, "Topology"):
9211
- print("Topology.SubTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
9376
+ if not Topology.IsInstance(topology, "Topology") and not Topology.IsInstance(topology, "Graph"):
9377
+ if not silent:
9378
+ print("Topology.SubTopologies - Error: the input topology parameter is not a valid topology. Returning None.")
9212
9379
  return None
9213
9380
  if Topology.TypeAsString(topology).lower() == subTopologyType.lower():
9214
9381
  return [topology]
9215
9382
 
9216
9383
  subTopologies = []
9217
9384
 
9385
+ # Special case for Graphs
9386
+ if Topology.IsInstance(topology, "graph"):
9387
+ if subTopologyType.lower() == "vertex":
9388
+ return Graph.Vertices(topology)
9389
+ elif subTopologyType.lower() == "edge":
9390
+ return Graph.Edges(topology)
9391
+ else:
9392
+ if not silent:
9393
+ print(f"Topology.SubTopologies - Error: the input subTopologyType parameter {subTopologyType} is not a valid subTopology of Graphs. Returning None.")
9394
+ return None
9395
+
9218
9396
  # Spcecial case for faces to return vertices in CW/CCW order.
9219
9397
  if Topology.IsInstance(topology, "face") and (subTopologyType.lower() == "vertex" or subTopologyType.lower() == "edge"):
9220
9398
  wires = Face.Wires(topology)
topologicpy/Vertex.py CHANGED
@@ -726,7 +726,7 @@ class Vertex():
726
726
  return None
727
727
 
728
728
  @staticmethod
729
- def EnclosingCell(vertex, topology, exclusive: bool = True, mantissa: int = 6, tolerance: float = 0.0001) -> list:
729
+ def EnclosingCells(vertex, topology, exclusive: bool = True, mantissa: int = 6, tolerance: float = 0.0001) -> list:
730
730
  """
731
731
  Returns the list of Cells found in the input topology that enclose the input vertex.
732
732