topologicpy 0.8.2__py3-none-any.whl → 0.8.4__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/Edge.py +44 -0
- topologicpy/Matrix.py +40 -0
- topologicpy/Plotly.py +4 -12
- topologicpy/Topology.py +395 -6
- topologicpy/version.py +1 -1
- {topologicpy-0.8.2.dist-info → topologicpy-0.8.4.dist-info}/METADATA +6 -6
- {topologicpy-0.8.2.dist-info → topologicpy-0.8.4.dist-info}/RECORD +10 -10
- {topologicpy-0.8.2.dist-info → topologicpy-0.8.4.dist-info}/WHEEL +1 -1
- {topologicpy-0.8.2.dist-info → topologicpy-0.8.4.dist-info}/LICENSE +0 -0
- {topologicpy-0.8.2.dist-info → topologicpy-0.8.4.dist-info}/top_level.txt +0 -0
topologicpy/Edge.py
CHANGED
@@ -325,6 +325,50 @@ class Edge():
|
|
325
325
|
edge = None
|
326
326
|
return edge
|
327
327
|
|
328
|
+
@staticmethod
|
329
|
+
def ByOriginDirectionLength(origin = None, direction=[0,0,1], length: float = 1.0, tolerance: float = 0.0001, silent: bool = False):
|
330
|
+
"""
|
331
|
+
Creates a straight edge from the input parameters.
|
332
|
+
|
333
|
+
Parameters
|
334
|
+
----------
|
335
|
+
origin : topologic_core.Vertex
|
336
|
+
The origin (start vertex) of the edge.
|
337
|
+
direction : list , optional
|
338
|
+
The desired direction vector of the edge. The default is [0,0,1] (pointing up in the Z direction)
|
339
|
+
length: float , optional
|
340
|
+
The desired length of edge. The default is 1.0.
|
341
|
+
tolerance : float , optional
|
342
|
+
The desired tolerance to decide if an edge can be created. The default is 0.0001.
|
343
|
+
silent : bool , optional
|
344
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
345
|
+
|
346
|
+
Returns
|
347
|
+
-------
|
348
|
+
topologic_core.Edge
|
349
|
+
The created edge.
|
350
|
+
|
351
|
+
"""
|
352
|
+
from topologicpy.Vertex import Vertex
|
353
|
+
from topologicpy.Topology import Topology
|
354
|
+
|
355
|
+
if origin == None:
|
356
|
+
origin = Vertex.Origin()
|
357
|
+
|
358
|
+
if not Topology.IsInstance(origin, "vertex"):
|
359
|
+
if not silent:
|
360
|
+
print("Edge.ByVertexDirectionLength - Error: The input vertex parameter is not a valid vertex. Returning None.")
|
361
|
+
return None
|
362
|
+
|
363
|
+
if length < tolerance:
|
364
|
+
if not silent:
|
365
|
+
print("Edge.ByVertexDirectionLength - Error: The input edge parameter must not be less than the input tolerance parameter. Returning None.")
|
366
|
+
return None
|
367
|
+
|
368
|
+
endVertex = Topology.TranslateByDirectionDistance(origin, direction=direction[:3], distance=length)
|
369
|
+
edge = Edge.ByVertices(origin, endVertex, tolerance=tolerance)
|
370
|
+
return edge
|
371
|
+
|
328
372
|
@staticmethod
|
329
373
|
def ByVertices(*args, tolerance: float = 0.0001, silent: bool = False):
|
330
374
|
"""
|
topologicpy/Matrix.py
CHANGED
@@ -204,6 +204,46 @@ class Matrix:
|
|
204
204
|
e_vectors = Helper.Sort(e_vectors, list(eigenvalues))
|
205
205
|
return e_values, e_vectors
|
206
206
|
|
207
|
+
@staticmethod
|
208
|
+
def Invert(matA, silent: bool = False):
|
209
|
+
"""
|
210
|
+
Inverts the input matrix.
|
211
|
+
|
212
|
+
Parameters
|
213
|
+
----------
|
214
|
+
matA : list of list of float
|
215
|
+
The input matrix.
|
216
|
+
silent : bool , optional
|
217
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
218
|
+
|
219
|
+
Returns
|
220
|
+
-------
|
221
|
+
list of list of float
|
222
|
+
The resulting matrix after it has been inverted.
|
223
|
+
|
224
|
+
"""
|
225
|
+
import numpy as np
|
226
|
+
|
227
|
+
if not isinstance(matA, list):
|
228
|
+
if not silent:
|
229
|
+
print("Matrix.Invert - Error: The input matA parameter is not a valid 4X4 matrix. Returning None.")
|
230
|
+
return None
|
231
|
+
np_matrix = np.array(matA)
|
232
|
+
if np_matrix.shape != (4, 4):
|
233
|
+
if not silent:
|
234
|
+
print("Matrix.Invert - Error: The input matA parameter is not a valid 4X4 matrix. Returning None.")
|
235
|
+
return None
|
236
|
+
|
237
|
+
# Check if the matrix is invertible
|
238
|
+
if np.isclose(np.linalg.det(np_matrix), 0):
|
239
|
+
if not silent:
|
240
|
+
print("Matrix.Invert - Error: The input matA parameter is not invertible. Returning None.")
|
241
|
+
return None
|
242
|
+
|
243
|
+
# Invert the matrix
|
244
|
+
inverted_matrix = np.linalg.inv(np_matrix)
|
245
|
+
return inverted_matrix.tolist()
|
246
|
+
|
207
247
|
@staticmethod
|
208
248
|
def Multiply(matA, matB):
|
209
249
|
"""
|
topologicpy/Plotly.py
CHANGED
@@ -714,15 +714,6 @@ class Plotly:
|
|
714
714
|
traces.append(trace)
|
715
715
|
return traces
|
716
716
|
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
717
|
@staticmethod
|
727
718
|
def DataByTopology(topology,
|
728
719
|
showVertices=True,
|
@@ -1106,9 +1097,10 @@ class Plotly:
|
|
1106
1097
|
faceColor = Dictionary.ValueAtKey(d, key=faceColorKey) or faceColor
|
1107
1098
|
if not faceOpacityKey == None:
|
1108
1099
|
d = Topology.Dictionary(topology)
|
1109
|
-
d_opacity = Dictionary.ValueAtKey(d, key=faceOpacityKey)
|
1110
|
-
if
|
1111
|
-
|
1100
|
+
d_opacity = Dictionary.ValueAtKey(d, key=faceOpacityKey)
|
1101
|
+
if not d_opacity == None:
|
1102
|
+
if 0 <= d_opacity <= 1:
|
1103
|
+
faceOpacity = d_opacity
|
1112
1104
|
if Topology.IsInstance(topology, "Face"):
|
1113
1105
|
tp_faces = [topology]
|
1114
1106
|
else:
|
topologicpy/Topology.py
CHANGED
@@ -4126,6 +4126,116 @@ class Topology():
|
|
4126
4126
|
return None
|
4127
4127
|
return Topology.ByXYZFile(file, frameIdKey=frameIdKey, vertexIdKey=frameIdKey)
|
4128
4128
|
|
4129
|
+
def CanonicalMatrix(topology, n: int = 10, normalize: bool = False, mantissa: int = 6, silent: bool = False):
|
4130
|
+
"""
|
4131
|
+
Returns the canonical matrix of the input topology.
|
4132
|
+
The canonical matrix refers to a transformation matrix that maps an object's
|
4133
|
+
coordinate system to a canonical coordinate frame, where:
|
4134
|
+
. The origin of the object aligns with the world origin
|
4135
|
+
. The principal axes of the object align with the world axes
|
4136
|
+
This transformation is computed using Principal Component Analysis (PCA),
|
4137
|
+
leveraging the eigenvectors of the covariance matrix of the object's vertices
|
4138
|
+
and thus can give erroneous results. The transformation matrix may not yield an object oriented as expected.
|
4139
|
+
|
4140
|
+
Parameters
|
4141
|
+
----------
|
4142
|
+
topology : topologic_core.Topology
|
4143
|
+
The input topology.
|
4144
|
+
n : int , optional
|
4145
|
+
The number of segments to use to increase the number of points on each face. The default is 10.
|
4146
|
+
normalize : bool , optional
|
4147
|
+
If set to True, the longest edge in the input topology is scaled to become of length 1. The default is False.
|
4148
|
+
mantissa : int , optional
|
4149
|
+
The desired length of the mantissa. The default is 6.
|
4150
|
+
silent : bool , optional
|
4151
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4152
|
+
|
4153
|
+
Returns
|
4154
|
+
-------
|
4155
|
+
list
|
4156
|
+
The 4X4 canonical matrix.
|
4157
|
+
|
4158
|
+
"""
|
4159
|
+
from topologicpy.Vertex import Vertex
|
4160
|
+
from topologicpy.Edge import Edge
|
4161
|
+
from topologicpy.CellComplex import CellComplex
|
4162
|
+
from topologicpy.Topology import Topology
|
4163
|
+
from topologicpy.Grid import Grid
|
4164
|
+
from topologicpy.Matrix import Matrix
|
4165
|
+
import numpy as np
|
4166
|
+
|
4167
|
+
def generate_floats(n):
|
4168
|
+
if n < 2:
|
4169
|
+
return [0.0] if n == 1 else []
|
4170
|
+
return [i / (n - 1) for i in range(n)]
|
4171
|
+
|
4172
|
+
if not Topology.IsInstance(topology, "topology"):
|
4173
|
+
if not silent:
|
4174
|
+
print("Topology.CanonicalMatrix - Error: The input topology parameter is not a valid topology. Returning None.")
|
4175
|
+
return None
|
4176
|
+
|
4177
|
+
faces = Topology.Faces(topology)
|
4178
|
+
if len(faces) == 0:
|
4179
|
+
if not silent:
|
4180
|
+
print("Topology.CanonicalMatrix - Error: The input topology parameter does not contain any faces. Returning None.")
|
4181
|
+
return None
|
4182
|
+
|
4183
|
+
# Step 1: Derive a copy topology to work with.
|
4184
|
+
if Topology.IsInstance(topology, "CellComplex"):
|
4185
|
+
top = CellComplex.ExternalBoundary(topology)
|
4186
|
+
else:
|
4187
|
+
top = Topology.Copy(topology)
|
4188
|
+
|
4189
|
+
# Step 2: Create a Translation Matrix to translate to origin
|
4190
|
+
centroid = Topology.Centroid(top)
|
4191
|
+
translation_matrix = Matrix.ByTranslation(-Vertex.X(centroid, mantissa=mantissa), -Vertex.Y(centroid, mantissa=mantissa), -Vertex.Z(centroid, mantissa=mantissa))
|
4192
|
+
translated_top = Topology.Translate(top, -Vertex.X(centroid, mantissa=mantissa), -Vertex.Y(centroid, mantissa=mantissa), -Vertex.Z(centroid, mantissa=mantissa))
|
4193
|
+
|
4194
|
+
# Step 3: Create a Scaling matrix to normalize size (e.g., largest edge length to 1)
|
4195
|
+
if normalize == False:
|
4196
|
+
scale_factor = 1.0
|
4197
|
+
else:
|
4198
|
+
edges = Topology.Edges(translated_top)
|
4199
|
+
max_edge_length = max([Edge.Length(edge, mantissa=mantissa) for edge in edges])
|
4200
|
+
scale_factor = 1.0 / max_edge_length if max_edge_length != 0 else 1.0
|
4201
|
+
scaling_matrix = Matrix.ByScaling(scaleX=scale_factor, scaleY=scale_factor, scaleZ=scale_factor)
|
4202
|
+
scaled_top = Topology.Scale(translated_top, origin=Vertex.Origin(), x=scale_factor, y=scale_factor, z=scale_factor)
|
4203
|
+
|
4204
|
+
# Step 4: Increase the number of vertices by adding a grid of points on each face.
|
4205
|
+
faces = Topology.Faces(scaled_top)
|
4206
|
+
vertices = Topology.Vertices(scaled_top)
|
4207
|
+
r = generate_floats(n)
|
4208
|
+
for face in faces:
|
4209
|
+
vertices += Topology.Vertices(Grid.VerticesByParameters(face=face, uRange=r, vRange=r, clip=True))
|
4210
|
+
points = np.array([[Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)] for v in vertices])
|
4211
|
+
|
4212
|
+
# Step 5: Align orientation using PCA
|
4213
|
+
# Compute PCA
|
4214
|
+
mean = points.mean(axis=0)
|
4215
|
+
centered_points = points - mean
|
4216
|
+
covariance_matrix = np.cov(centered_points.T)
|
4217
|
+
eigenvalues, eigenvectors = np.linalg.eigh(covariance_matrix)
|
4218
|
+
|
4219
|
+
# Step 6: Sort eigenvectors by eigenvalues (largest first)
|
4220
|
+
sorted_indices = np.argsort(-eigenvalues)
|
4221
|
+
eigenvectors = eigenvectors[:, sorted_indices]
|
4222
|
+
|
4223
|
+
# Step 7: Enforce consistent orientation by flipping eigenvectors
|
4224
|
+
for i in range(3): # Ensure each eigenvector points in a positive direction
|
4225
|
+
if np.dot(eigenvectors[:, i], [1, 0, 0]) < 0:
|
4226
|
+
eigenvectors[:, i] *= -1
|
4227
|
+
|
4228
|
+
# Step 8: Create the rotation matrix
|
4229
|
+
rotation_matrix = np.eye(4)
|
4230
|
+
rotation_matrix[:3, :3] = eigenvectors.T # Use transpose to align points to axes
|
4231
|
+
|
4232
|
+
# Step 9: Rotate the object to align it
|
4233
|
+
transformation_matrix = Matrix.Multiply(scaling_matrix, translation_matrix)
|
4234
|
+
transformation_matrix = Matrix.Multiply(rotation_matrix.tolist(), transformation_matrix)
|
4235
|
+
|
4236
|
+
# Step 10: Return the resulting matrix
|
4237
|
+
return transformation_matrix
|
4238
|
+
|
4129
4239
|
@staticmethod
|
4130
4240
|
def CenterOfMass(topology):
|
4131
4241
|
"""
|
@@ -6492,9 +6602,9 @@ class Topology():
|
|
6492
6602
|
return None
|
6493
6603
|
return topologic.Topology.IsSame(topologyA, topologyB)
|
6494
6604
|
|
6495
|
-
def
|
6605
|
+
def IsVertexCongruent(topologyA, topologyB, mantissa: int = 6, tolerance=0.0001, silent : bool = False):
|
6496
6606
|
"""
|
6497
|
-
Returns True if the input topologies are vertex matched (have same number of vertices and all vertices are
|
6607
|
+
Returns True if the input topologies are vertex matched (have same number of vertices and all vertices are congruent within a tolerance). Returns False otherwise.
|
6498
6608
|
|
6499
6609
|
Parameters
|
6500
6610
|
----------
|
@@ -6512,7 +6622,7 @@ class Topology():
|
|
6512
6622
|
Returns
|
6513
6623
|
-------
|
6514
6624
|
bool
|
6515
|
-
True of the input topologies are vertex
|
6625
|
+
True of the input topologies are vertex congruent. False otherwise.
|
6516
6626
|
|
6517
6627
|
"""
|
6518
6628
|
from topologicpy.Vertex import Vertex
|
@@ -6573,6 +6683,104 @@ class Topology():
|
|
6573
6683
|
coords_b = [Vertex.Coordinates(v, mantissa=mantissa) for v in vertices_b]
|
6574
6684
|
return coordinates_match(coords_a, coords_b, tolerance=tolerance)
|
6575
6685
|
|
6686
|
+
@staticmethod
|
6687
|
+
def LargestFaces(topology, removeCoplanarFaces: bool = False, epsilon: float = 0.001, tolerance: float = 0.0001, silent: bool = False):
|
6688
|
+
"""
|
6689
|
+
Returns the list of the largest faces found in the input topology.
|
6690
|
+
|
6691
|
+
Parameters
|
6692
|
+
----------
|
6693
|
+
topology : topologic_core.Topology
|
6694
|
+
The input topology.
|
6695
|
+
removeCoplanarFaces : bool , optional
|
6696
|
+
If set to True, coplanar faces are removed to find the true largest faces. Otherwise they are not. The default is False.
|
6697
|
+
epsilon : float , optional
|
6698
|
+
The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
|
6699
|
+
tolerance : float , optional
|
6700
|
+
The desired tolerance. The default is 0.0001.
|
6701
|
+
silent : bool , optional
|
6702
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6703
|
+
Returns
|
6704
|
+
-------
|
6705
|
+
list
|
6706
|
+
The list of the largest faces found in the input topology.
|
6707
|
+
|
6708
|
+
"""
|
6709
|
+
from topologicpy.Face import Face
|
6710
|
+
|
6711
|
+
if not Topology.IsInstance(topology, "topology"):
|
6712
|
+
if not silent:
|
6713
|
+
print("Topology.LaregestFaces - Error: The input topology parameter is not a valid topology. Returning None.")
|
6714
|
+
return None
|
6715
|
+
faces = Topology.Faces(topology)
|
6716
|
+
if len(faces) == 0:
|
6717
|
+
if not silent:
|
6718
|
+
print("Topology.LargestFaces - Error: The input topology parameter does not contain any faces. Returning None.")
|
6719
|
+
return None
|
6720
|
+
if len(faces) == 1:
|
6721
|
+
return faces
|
6722
|
+
|
6723
|
+
if removeCoplanarFaces == True:
|
6724
|
+
simple_topology = Topology.RemoveCoplanarFaces(topology)
|
6725
|
+
simple_topology = Topology.RemoveCollinearEdges(simple_topology)
|
6726
|
+
faces = Topology.Faces(simple_topology)
|
6727
|
+
face_areas = [Face.Area(face) for face in faces]
|
6728
|
+
max_area = max(face_areas)
|
6729
|
+
max_faces = []
|
6730
|
+
for i, face_area in enumerate(face_areas):
|
6731
|
+
if abs(max_area - face_area) < tolerance:
|
6732
|
+
max_faces.append(faces[i])
|
6733
|
+
return max_faces
|
6734
|
+
|
6735
|
+
@staticmethod
|
6736
|
+
def LongestEdges(topology, removeCoplanarFaces: bool = False, epsilon: float = 0.001, tolerance: float = 0.0001, silent: bool = False):
|
6737
|
+
"""
|
6738
|
+
Returns the list of the longest edges found in the input topology.
|
6739
|
+
|
6740
|
+
Parameters
|
6741
|
+
----------
|
6742
|
+
topology : topologic_core.Topology
|
6743
|
+
The input topology.
|
6744
|
+
removeCoplanarFaces : bool , optional
|
6745
|
+
If set to True, coplanar faces are removed to find the true longest straight edges. Otherwise they are not. The default is False.
|
6746
|
+
epsilon : float , optional
|
6747
|
+
The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
|
6748
|
+
tolerance : float , optional
|
6749
|
+
The desired tolerance. The default is 0.0001.
|
6750
|
+
silent : bool , optional
|
6751
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6752
|
+
Returns
|
6753
|
+
-------
|
6754
|
+
list
|
6755
|
+
The list of of the longest edges found in the input topology.
|
6756
|
+
|
6757
|
+
"""
|
6758
|
+
from topologicpy.Edge import Edge
|
6759
|
+
|
6760
|
+
if not Topology.IsInstance(topology, "topology"):
|
6761
|
+
if not silent:
|
6762
|
+
print("Topology.LongestEdges - Error: The input topology parameter is not a valid topology. Returning None.")
|
6763
|
+
return None
|
6764
|
+
edges = Topology.Edges(topology)
|
6765
|
+
if len(edges) == 0:
|
6766
|
+
if not silent:
|
6767
|
+
print("Topology.LongestEdges - Error: The input topology parameter does not contain any edges. Returning None.")
|
6768
|
+
return None
|
6769
|
+
if len(edges) == 1:
|
6770
|
+
return edges
|
6771
|
+
|
6772
|
+
if removeCoplanarFaces == True:
|
6773
|
+
simple_topology = Topology.RemoveCoplanarFaces(topology)
|
6774
|
+
simple_topology = Topology.RemoveCollinearEdges(simple_topology)
|
6775
|
+
edges = Topology.Edges(simple_topology)
|
6776
|
+
edge_lengths = [Edge.Length(edge) for edge in edges]
|
6777
|
+
max_length = max(edge_lengths)
|
6778
|
+
max_edges = []
|
6779
|
+
for i, edge_length in enumerate(edge_lengths):
|
6780
|
+
if abs(max_length - edge_length) < tolerance:
|
6781
|
+
max_edges.append(edges[i])
|
6782
|
+
return max_edges
|
6783
|
+
|
6576
6784
|
@staticmethod
|
6577
6785
|
def MergeAll(topologies, tolerance=0.0001):
|
6578
6786
|
"""
|
@@ -6833,7 +7041,7 @@ class Topology():
|
|
6833
7041
|
originB : topologic_core.Vertex , optional
|
6834
7042
|
The new location at which to place the topology. If set to None, the world origin (0, 0, 0) is used. The default is None.
|
6835
7043
|
mantissa : int , optional
|
6836
|
-
The desired length of the mantissa. The default is 6
|
7044
|
+
The desired length of the mantissa. The default is 6.
|
6837
7045
|
|
6838
7046
|
Returns
|
6839
7047
|
-------
|
@@ -6860,6 +7068,90 @@ class Topology():
|
|
6860
7068
|
newTopology = None
|
6861
7069
|
return newTopology
|
6862
7070
|
|
7071
|
+
@staticmethod
|
7072
|
+
def PrincipalAxes(topology, n: int = 10, mantissa: int = 6, silent: bool = False):
|
7073
|
+
"""
|
7074
|
+
Returns the prinicipal axes (vectors) of the input topology.
|
7075
|
+
Please note that this is not a perfect algorithm and it can get confused based on the geometry of the input.
|
7076
|
+
Also, please note that there is no guarantee that three returned vectors match your expectation for an x,y,z axis order.
|
7077
|
+
|
7078
|
+
Parameters
|
7079
|
+
----------
|
7080
|
+
topology : topologic_core.Topology
|
7081
|
+
The input topology.
|
7082
|
+
n : int , optional
|
7083
|
+
The number of segments to use to increase the number of points on each face. The default is 10.
|
7084
|
+
mantissa : int , optional
|
7085
|
+
The desired length of the mantissa. The default is 6.
|
7086
|
+
silent : bool , optional
|
7087
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7088
|
+
|
7089
|
+
Returns
|
7090
|
+
-------
|
7091
|
+
list
|
7092
|
+
The list of x, y, and z vectors representing the principal axes of the topology.
|
7093
|
+
|
7094
|
+
"""
|
7095
|
+
from topologicpy.Vertex import Vertex
|
7096
|
+
from topologicpy.CellComplex import CellComplex
|
7097
|
+
from topologicpy.Topology import Topology
|
7098
|
+
from topologicpy.Grid import Grid
|
7099
|
+
from topologicpy.Vector import Vector
|
7100
|
+
import numpy as np
|
7101
|
+
|
7102
|
+
def generate_floats(n):
|
7103
|
+
if n < 2:
|
7104
|
+
return [0.0] if n == 1 else []
|
7105
|
+
return [i / (n - 1) for i in range(n)]
|
7106
|
+
|
7107
|
+
|
7108
|
+
if not Topology.IsInstance(topology, "topology"):
|
7109
|
+
if not silent:
|
7110
|
+
print("Topology.CanonicalMatrix - Error: The input topology parameter is not a valid topology. Returning None.")
|
7111
|
+
return None
|
7112
|
+
|
7113
|
+
faces = Topology.Faces(topology)
|
7114
|
+
if len(faces) == 0:
|
7115
|
+
if not silent:
|
7116
|
+
print("Topology.CanonicalMatrix - Error: The input topology parameter does not contain any faces. Returning None.")
|
7117
|
+
return None
|
7118
|
+
|
7119
|
+
# Step 1: Derive a copy topology to work with.
|
7120
|
+
if Topology.IsInstance(topology, "CellComplex"):
|
7121
|
+
top = CellComplex.ExternalBoundary(topology)
|
7122
|
+
else:
|
7123
|
+
top = Topology.Copy(topology)
|
7124
|
+
|
7125
|
+
# Step 2: Increase the number of vertices by adding a grid of points on each face.
|
7126
|
+
faces = Topology.Faces(top)
|
7127
|
+
vertices = Topology.Vertices(top)
|
7128
|
+
r = generate_floats(n)
|
7129
|
+
for face in faces:
|
7130
|
+
vertices += Topology.Vertices(Grid.VerticesByParameters(face=face, uRange=r, vRange=r, clip=True))
|
7131
|
+
points = np.array([[Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)] for v in vertices])
|
7132
|
+
|
7133
|
+
# Step 3: Align orientation using PCA
|
7134
|
+
# Compute PCA
|
7135
|
+
mean = points.mean(axis=0)
|
7136
|
+
centered_points = points - mean
|
7137
|
+
covariance_matrix = np.cov(centered_points.T)
|
7138
|
+
eigenvalues, eigenvectors = np.linalg.eigh(covariance_matrix)
|
7139
|
+
|
7140
|
+
# Sort eigenvectors by eigenvalues (largest first)
|
7141
|
+
sorted_indices = np.argsort(-eigenvalues)
|
7142
|
+
eigenvectors = eigenvectors[:, sorted_indices]
|
7143
|
+
|
7144
|
+
# Enforce consistent orientation by flipping eigenvectors
|
7145
|
+
for i in range(3): # Ensure each eigenvector points in a positive direction
|
7146
|
+
if np.dot(eigenvectors[:, i], [1, 0, 0]) < 0:
|
7147
|
+
eigenvectors[:, i] *= -1
|
7148
|
+
|
7149
|
+
# Retrieve and return the principal axes
|
7150
|
+
x_axis = Vector.ByCoordinates(*eigenvectors[:, 0])
|
7151
|
+
y_axis = Vector.ByCoordinates(*eigenvectors[:, 1])
|
7152
|
+
z_axis = Vector.ByCoordinates(*eigenvectors[:, 2])
|
7153
|
+
return x_axis, y_axis, z_axis
|
7154
|
+
|
6863
7155
|
@staticmethod
|
6864
7156
|
def RemoveCollinearEdges(topology, angTolerance: float = 0.1, tolerance: float = 0.0001, silent: bool = False):
|
6865
7157
|
"""
|
@@ -7978,7 +8270,6 @@ class Topology():
|
|
7978
8270
|
l = None
|
7979
8271
|
return l
|
7980
8272
|
|
7981
|
-
|
7982
8273
|
@staticmethod
|
7983
8274
|
def SharedFaces(topologyA, topologyB):
|
7984
8275
|
"""
|
@@ -8005,7 +8296,56 @@ class Topology():
|
|
8005
8296
|
except:
|
8006
8297
|
l = None
|
8007
8298
|
return l
|
8008
|
-
|
8299
|
+
|
8300
|
+
@staticmethod
|
8301
|
+
def ShortestEdges(topology, removeCoplanarFaces: bool = False, epsilon: float = 0.001, tolerance: float = 0.0001, silent: bool = False):
|
8302
|
+
"""
|
8303
|
+
Returns the list of the shortest edges found in the input topology.
|
8304
|
+
|
8305
|
+
Parameters
|
8306
|
+
----------
|
8307
|
+
topology : topologic_core.Topology
|
8308
|
+
The input topology.
|
8309
|
+
removeCoplanarFaces : bool , optional
|
8310
|
+
If set to True, coplanar faces are removed to find the true shortest straight edges. Otherwise they are not. The default is False.
|
8311
|
+
epsilon : float , optional
|
8312
|
+
The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
|
8313
|
+
tolerance : float , optional
|
8314
|
+
The desired tolerance. The default is 0.0001.
|
8315
|
+
silent : bool , optional
|
8316
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
8317
|
+
Returns
|
8318
|
+
-------
|
8319
|
+
list
|
8320
|
+
The list of the shortest edges found in the input topology.
|
8321
|
+
|
8322
|
+
"""
|
8323
|
+
from topologicpy.Edge import Edge
|
8324
|
+
|
8325
|
+
if not Topology.IsInstance(topology, "topology"):
|
8326
|
+
if not silent:
|
8327
|
+
print("Topology.ShortestEdges - Error: The input topology parameter is not a valid topology. Returning None.")
|
8328
|
+
return None
|
8329
|
+
edges = Topology.Edges(topology)
|
8330
|
+
if len(edges) == 0:
|
8331
|
+
if not silent:
|
8332
|
+
print("Topology.ShortestEdges - Error: The input topology parameter does not contain any edges. Returning None.")
|
8333
|
+
return None
|
8334
|
+
if len(edges) == 1:
|
8335
|
+
return edges[0]
|
8336
|
+
|
8337
|
+
if removeCoplanarFaces == True:
|
8338
|
+
simple_topology = Topology.RemoveCoplanarFaces(topology)
|
8339
|
+
simple_topology = Topology.RemoveCollinearEdges(simple_topology)
|
8340
|
+
edges = Topology.Edges(simple_topology)
|
8341
|
+
edge_lengths = [Edge.Length(edge) for edge in edges]
|
8342
|
+
min_length = min(edge_lengths)
|
8343
|
+
min_edges = []
|
8344
|
+
for i, edge_length in enumerate(edge_lengths):
|
8345
|
+
if abs(min_length - edge_length) < tolerance:
|
8346
|
+
min_edges.append(edges[i])
|
8347
|
+
return min_edges
|
8348
|
+
|
8009
8349
|
@staticmethod
|
8010
8350
|
def Show(*topologies,
|
8011
8351
|
nameKey = "name",
|
@@ -8424,6 +8764,55 @@ class Topology():
|
|
8424
8764
|
figure = Plotly.AddColorBar(figure, values=cbValues, nTicks=cbTicks, xPosition=cbX, width=cbWidth, outlineWidth=cbOutlineWidth, title=cbTitle, subTitle=cbSubTitle, units=cbUnits, colorScale=colorScale, mantissa=mantissa)
|
8425
8765
|
Plotly.Show(figure=figure, renderer=renderer, camera=camera, center=center, up=up, projection=projection)
|
8426
8766
|
|
8767
|
+
@staticmethod
|
8768
|
+
def SmallestFaces(topology, removeCoplanarFaces: bool = False, epsilon: float = 0.001, tolerance: float = 0.0001, silent: bool = False):
|
8769
|
+
"""
|
8770
|
+
Returns the list of the smallest faces found in the input topology.
|
8771
|
+
|
8772
|
+
Parameters
|
8773
|
+
----------
|
8774
|
+
topology : topologic_core.Topology
|
8775
|
+
The input topology.
|
8776
|
+
removeCoplanarFaces : bool , optional
|
8777
|
+
If set to True, coplanar faces are removed to find the true smallest faces. Otherwise they are not. The default is False.
|
8778
|
+
epsilon : float , optional
|
8779
|
+
The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
|
8780
|
+
tolerance : float , optional
|
8781
|
+
The desired tolerance. The default is 0.0001.
|
8782
|
+
silent : bool , optional
|
8783
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
8784
|
+
Returns
|
8785
|
+
-------
|
8786
|
+
list
|
8787
|
+
The list of the smallest faces found in the input topology.
|
8788
|
+
|
8789
|
+
"""
|
8790
|
+
from topologicpy.Face import Face
|
8791
|
+
|
8792
|
+
if not Topology.IsInstance(topology, "topology"):
|
8793
|
+
if not silent:
|
8794
|
+
print("Topology.SmallestFaces - Error: The input topology parameter is not a valid topology. Returning None.")
|
8795
|
+
return None
|
8796
|
+
faces = Topology.Faces(topology)
|
8797
|
+
if len(faces) == 0:
|
8798
|
+
if not silent:
|
8799
|
+
print("Topology.SmallestFaces - Error: The input topology parameter does not contain any faces. Returning None.")
|
8800
|
+
return None
|
8801
|
+
if len(faces) == 1:
|
8802
|
+
return faces
|
8803
|
+
|
8804
|
+
if removeCoplanarFaces == True:
|
8805
|
+
simple_topology = Topology.RemoveCoplanarFaces(topology)
|
8806
|
+
simple_topology = Topology.RemoveCollinearEdges(simple_topology)
|
8807
|
+
faces = Topology.Faces(simple_topology)
|
8808
|
+
face_areas = [Face.Area(face) for face in faces]
|
8809
|
+
min_area = min(face_areas)
|
8810
|
+
min_faces = []
|
8811
|
+
for i, face_area in enumerate(face_areas):
|
8812
|
+
if abs(min_area - face_area) < tolerance:
|
8813
|
+
min_faces.append(faces[i])
|
8814
|
+
return min_faces
|
8815
|
+
|
8427
8816
|
@staticmethod
|
8428
8817
|
def SortBySelectors(topologies, selectors, exclusive=False, tolerance=0.0001):
|
8429
8818
|
"""
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.8.
|
1
|
+
__version__ = '0.8.4'
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.4
|
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
|
@@ -115,12 +115,12 @@ To cite one of the main papers that defines topologicpy, you can use:
|
|
115
115
|
|
116
116
|
Or you can import the following .bib formatted references into your favourite reference manager
|
117
117
|
```
|
118
|
-
@misc{
|
118
|
+
@misc{Jabi2025,
|
119
119
|
author = {Wassim Jabi},
|
120
120
|
doi = {https://doi.org/10.5281/zenodo.11555173},
|
121
121
|
title = {topologicpy},
|
122
122
|
url = {http://pypi.org/projects/topologicpy},
|
123
|
-
year = {
|
123
|
+
year = {2025},
|
124
124
|
}
|
125
125
|
```
|
126
126
|
```
|
@@ -140,6 +140,6 @@ Or you can import the following .bib formatted references into your favourite re
|
|
140
140
|
}
|
141
141
|
```
|
142
142
|
|
143
|
-
topologicpy: ©
|
143
|
+
topologicpy: © 2025 Wassim Jabi
|
144
144
|
|
145
|
-
Topologic: ©
|
145
|
+
Topologic: © 2025 Cardiff University and UCL
|
@@ -8,29 +8,29 @@ topologicpy/Color.py,sha256=q9xsGmxFMz7sQKmygwSVS12GaTRB-OT0-_i6t3-cthE,20307
|
|
8
8
|
topologicpy/Context.py,sha256=ppApYKngZZCQBFWaxIMi2z2dokY23c935IDCBosxDAE,3055
|
9
9
|
topologicpy/DGL.py,sha256=M_znFtyPBJJz-iXLYZs2wwBj24fhevIo739dGha0chM,139041
|
10
10
|
topologicpy/Dictionary.py,sha256=t0O7Du-iPq46FyKqZfcjHfsUK1E8GS_e67R2V5cpkbw,33186
|
11
|
-
topologicpy/Edge.py,sha256=
|
11
|
+
topologicpy/Edge.py,sha256=lWwJdQkAhiH5POB7TN6HSLv03g2jXHzBU7e2fE3eAno,71340
|
12
12
|
topologicpy/EnergyModel.py,sha256=UoQ9Jm-hYsN383CbcLKw-y6BKitRHj0uyh84yQ-8ACg,53856
|
13
13
|
topologicpy/Face.py,sha256=D1g4O5i5QMPZvIoX06Z-FsyNsYBDkCiHWJp00uqnr5o,180742
|
14
14
|
topologicpy/Graph.py,sha256=T_NC-Gvf7F7DWdfWvQB7sQ_v790lTT74SLKTl-UhSZE,492072
|
15
15
|
topologicpy/Grid.py,sha256=2s9cSlWldivn1i9EUz4OOokJyANveqmRe_vR93CAndI,18245
|
16
16
|
topologicpy/Helper.py,sha256=DAAE_Ie_ekeMnCvcW08xXRwSAGCkjrS4lbz-o3ELuY4,27172
|
17
17
|
topologicpy/Honeybee.py,sha256=Y_El6M8x3ixvvIe_VcRiwj_4C89ZZg5_WlT7adbCkpw,21849
|
18
|
-
topologicpy/Matrix.py,sha256=
|
18
|
+
topologicpy/Matrix.py,sha256=ydw0EH4rZcGBFeLmBHPIyuk57DVKKL3M1GcArkFsYxM,10941
|
19
19
|
topologicpy/Neo4j.py,sha256=BKOF29fRgXmdpMGkrNzuYbyqgCJ6ElPPMYlfTxXiVbc,22392
|
20
|
-
topologicpy/Plotly.py,sha256=
|
20
|
+
topologicpy/Plotly.py,sha256=xfd_c2Mcam5KP-gDD-esl42RVXW5TSJsUCCqhUg1VFk,115788
|
21
21
|
topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
|
22
22
|
topologicpy/PyG.py,sha256=LU9LCCzjxGPUM31qbaJXZsTvniTtgugxJY7y612t4A4,109757
|
23
23
|
topologicpy/Shell.py,sha256=fLRnQ79vtdBDRW1Xn8Gaap34XheGbw7UBFd-ALJ2Y1g,87978
|
24
24
|
topologicpy/Speckle.py,sha256=AlsGlSDuKRtX5jhVsPNSSjjbZis079HbUchDH_5RJmE,18187
|
25
25
|
topologicpy/Sun.py,sha256=42tDWMYpwRG7Z2Qjtp94eRgBuqySq7k8TgNUZDK7QxQ,36837
|
26
|
-
topologicpy/Topology.py,sha256
|
26
|
+
topologicpy/Topology.py,sha256=IHLJxh0TQ7P8Git4qfFDlvas81JXaKQGjxwgBzIX1T0,463367
|
27
27
|
topologicpy/Vector.py,sha256=Cl7besf20cAGmyNPh-9gbFAHnRU5ZWSMChJ3VyFIDs4,35416
|
28
28
|
topologicpy/Vertex.py,sha256=tv6C-rbuNgXHDGgVLT5fbalynLdXqlUuiCDKtkeQ0vk,77814
|
29
29
|
topologicpy/Wire.py,sha256=Gl3Jpygwp8775SG57ua5r5ffTHcN4FOAkeI87yP1cok,234001
|
30
30
|
topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
|
31
|
-
topologicpy/version.py,sha256=
|
32
|
-
topologicpy-0.8.
|
33
|
-
topologicpy-0.8.
|
34
|
-
topologicpy-0.8.
|
35
|
-
topologicpy-0.8.
|
36
|
-
topologicpy-0.8.
|
31
|
+
topologicpy/version.py,sha256=PtH7P3DXedvpK4LWSSxCTW38cmEXvmG6EvUpXZC-eLo,22
|
32
|
+
topologicpy-0.8.4.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
33
|
+
topologicpy-0.8.4.dist-info/METADATA,sha256=WsO5JswYFeodLDlAb5Jv9TnaIhSl0z2ge2fhH-3hLpw,10512
|
34
|
+
topologicpy-0.8.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
35
|
+
topologicpy-0.8.4.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
36
|
+
topologicpy-0.8.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|