topologicpy 0.8.0__py3-none-any.whl → 0.8.3__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/Cluster.py +67 -0
- topologicpy/Edge.py +101 -0
- topologicpy/Face.py +160 -4
- topologicpy/Helper.py +158 -44
- topologicpy/Matrix.py +85 -73
- topologicpy/Topology.py +279 -4
- topologicpy/Wire.py +239 -43
- topologicpy/version.py +1 -1
- {topologicpy-0.8.0.dist-info → topologicpy-0.8.3.dist-info}/METADATA +1 -1
- {topologicpy-0.8.0.dist-info → topologicpy-0.8.3.dist-info}/RECORD +13 -13
- {topologicpy-0.8.0.dist-info → topologicpy-0.8.3.dist-info}/WHEEL +1 -1
- {topologicpy-0.8.0.dist-info → topologicpy-0.8.3.dist-info}/LICENSE +0 -0
- {topologicpy-0.8.0.dist-info → topologicpy-0.8.3.dist-info}/top_level.txt +0 -0
topologicpy/Matrix.py
CHANGED
@@ -49,70 +49,47 @@ class Matrix:
|
|
49
49
|
|
50
50
|
@staticmethod
|
51
51
|
def ByRotation(angleX=0, angleY=0, angleZ=0, order="xyz"):
|
52
|
-
"""
|
53
|
-
Creates a 4x4 rotation matrix.
|
54
|
-
|
55
|
-
Parameters
|
56
|
-
----------
|
57
|
-
angleX : float , optional
|
58
|
-
The desired rotation angle in degrees around the X axis. The default is 0.
|
59
|
-
angleY : float , optional
|
60
|
-
The desired rotation angle in degrees around the Y axis. The default is 0.
|
61
|
-
angleZ : float , optional
|
62
|
-
The desired rotation angle in degrees around the Z axis. The default is 0.
|
63
|
-
order : string , optional
|
64
|
-
The order by which the roatations will be applied. The possible values are any permutation of "xyz". This input is case insensitive. The default is "xyz".
|
65
|
-
|
66
|
-
Returns
|
67
|
-
-------
|
68
|
-
list
|
69
|
-
The created 4X4 rotation matrix.
|
70
|
-
|
71
|
-
"""
|
72
52
|
def rotateXMatrix(radians):
|
73
|
-
""" Return matrix for rotating about the x-axis by 'radians' radians """
|
74
53
|
c = math.cos(radians)
|
75
54
|
s = math.sin(radians)
|
76
55
|
return [[1, 0, 0, 0],
|
77
|
-
[0, c
|
56
|
+
[0, c, -s, 0],
|
78
57
|
[0, s, c, 0],
|
79
58
|
[0, 0, 0, 1]]
|
80
59
|
|
81
60
|
def rotateYMatrix(radians):
|
82
|
-
""" Return matrix for rotating about the y-axis by 'radians' radians """
|
83
|
-
|
84
61
|
c = math.cos(radians)
|
85
62
|
s = math.sin(radians)
|
86
|
-
return [[
|
87
|
-
[
|
63
|
+
return [[c, 0, s, 0],
|
64
|
+
[0, 1, 0, 0],
|
88
65
|
[-s, 0, c, 0],
|
89
|
-
[
|
66
|
+
[0, 0, 0, 1]]
|
90
67
|
|
91
68
|
def rotateZMatrix(radians):
|
92
|
-
""" Return matrix for rotating about the z-axis by 'radians' radians """
|
93
|
-
|
94
69
|
c = math.cos(radians)
|
95
70
|
s = math.sin(radians)
|
96
|
-
return [[c
|
71
|
+
return [[c, -s, 0, 0],
|
97
72
|
[s, c, 0, 0],
|
98
73
|
[0, 0, 1, 0],
|
99
74
|
[0, 0, 0, 1]]
|
100
|
-
|
75
|
+
|
101
76
|
xMat = rotateXMatrix(math.radians(angleX))
|
102
77
|
yMat = rotateYMatrix(math.radians(angleY))
|
103
78
|
zMat = rotateZMatrix(math.radians(angleZ))
|
79
|
+
|
104
80
|
if order.lower() == "xyz":
|
105
|
-
return Matrix.Multiply(Matrix.Multiply(zMat,yMat),xMat)
|
81
|
+
return Matrix.Multiply(Matrix.Multiply(zMat, yMat), xMat)
|
106
82
|
if order.lower() == "xzy":
|
107
|
-
return Matrix.Multiply(Matrix.Multiply(yMat,zMat),xMat)
|
83
|
+
return Matrix.Multiply(Matrix.Multiply(yMat, zMat), xMat)
|
108
84
|
if order.lower() == "yxz":
|
109
|
-
return Matrix.Multiply(Matrix.Multiply(zMat,xMat),yMat)
|
110
|
-
if order.lower == "yzx":
|
111
|
-
return Matrix.Multiply(Matrix.Multiply(xMat,zMat),yMat)
|
85
|
+
return Matrix.Multiply(Matrix.Multiply(zMat, xMat), yMat)
|
86
|
+
if order.lower() == "yzx":
|
87
|
+
return Matrix.Multiply(Matrix.Multiply(xMat, zMat), yMat)
|
112
88
|
if order.lower() == "zxy":
|
113
|
-
return Matrix.Multiply(Matrix.Multiply(yMat,xMat),zMat)
|
89
|
+
return Matrix.Multiply(Matrix.Multiply(yMat, xMat), zMat)
|
114
90
|
if order.lower() == "zyx":
|
115
|
-
return Matrix.Multiply(Matrix.Multiply(xMat,yMat),zMat)
|
91
|
+
return Matrix.Multiply(Matrix.Multiply(xMat, yMat), zMat)
|
92
|
+
|
116
93
|
|
117
94
|
@staticmethod
|
118
95
|
def ByScaling(scaleX=1.0, scaleY=1.0, scaleZ=1.0):
|
@@ -159,10 +136,10 @@ class Matrix:
|
|
159
136
|
The created 4X4 translation matrix.
|
160
137
|
|
161
138
|
"""
|
162
|
-
return [[1,0,0,
|
163
|
-
[0,1,0,
|
164
|
-
[0,0,1,
|
165
|
-
[
|
139
|
+
return [[1,0,0,translateX],
|
140
|
+
[0,1,0,translateY],
|
141
|
+
[0,0,1,translateZ],
|
142
|
+
[0,0,0,1]]
|
166
143
|
|
167
144
|
@staticmethod
|
168
145
|
def EigenvaluesAndVectors(matrix, mantissa: int = 6, silent: bool = False):
|
@@ -228,49 +205,84 @@ class Matrix:
|
|
228
205
|
return e_values, e_vectors
|
229
206
|
|
230
207
|
@staticmethod
|
231
|
-
def
|
208
|
+
def Invert(matA, silent: bool = False):
|
232
209
|
"""
|
233
|
-
|
234
|
-
|
235
|
-
|
210
|
+
Inverts the input matrix.
|
211
|
+
|
236
212
|
Parameters
|
237
213
|
----------
|
238
|
-
matA : list
|
239
|
-
The
|
240
|
-
|
241
|
-
The
|
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.
|
242
218
|
|
243
219
|
Returns
|
244
220
|
-------
|
245
|
-
list
|
246
|
-
The
|
221
|
+
list of list of float
|
222
|
+
The resulting matrix after it has been inverted.
|
247
223
|
|
248
224
|
"""
|
225
|
+
import numpy as np
|
226
|
+
|
249
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.")
|
250
230
|
return None
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
matC = []
|
256
|
-
for i in range(nr):
|
257
|
-
tempRow = []
|
258
|
-
for j in range(nc):
|
259
|
-
tempRow.append(0)
|
260
|
-
matC.append(tempRow)
|
261
|
-
if not isinstance(matA, list):
|
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.")
|
262
235
|
return None
|
263
|
-
|
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.")
|
264
241
|
return None
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
242
|
+
|
243
|
+
# Invert the matrix
|
244
|
+
inverted_matrix = np.linalg.inv(np_matrix)
|
245
|
+
return inverted_matrix.tolist()
|
246
|
+
|
247
|
+
@staticmethod
|
248
|
+
def Multiply(matA, matB):
|
249
|
+
"""
|
250
|
+
Multiplies two matrices (matA and matB). The first matrix (matA) is applied first in the transformation,
|
251
|
+
followed by the second matrix (matB).
|
252
|
+
|
253
|
+
Parameters
|
254
|
+
----------
|
255
|
+
matA : list of list of float
|
256
|
+
The first input matrix.
|
257
|
+
matB : list of list of float
|
258
|
+
The second input matrix.
|
259
|
+
|
260
|
+
Returns
|
261
|
+
-------
|
262
|
+
list of list of float
|
263
|
+
The resulting matrix after multiplication.
|
264
|
+
|
265
|
+
"""
|
266
|
+
# Input validation
|
267
|
+
if not (isinstance(matA, list) and all(isinstance(row, list) for row in matA) and
|
268
|
+
isinstance(matB, list) and all(isinstance(row, list) for row in matB)):
|
269
|
+
raise ValueError("Both inputs must be 2D lists representing matrices.")
|
270
|
+
|
271
|
+
# Check matrix dimension compatibility
|
272
|
+
if len(matA[0]) != len(matB):
|
273
|
+
raise ValueError("Number of columns in matA must equal the number of rows in matB.")
|
274
|
+
|
275
|
+
# Dimensions of the resulting matrix
|
276
|
+
rows_A, cols_A = len(matA), len(matA[0])
|
277
|
+
rows_B, cols_B = len(matB), len(matB[0])
|
278
|
+
result = [[0.0] * cols_B for _ in range(rows_A)]
|
279
|
+
|
280
|
+
# Matrix multiplication
|
281
|
+
for i in range(rows_A):
|
282
|
+
for j in range(cols_B):
|
283
|
+
result[i][j] = sum(matA[i][k] * matB[k][j] for k in range(cols_A))
|
284
|
+
|
285
|
+
return result
|
274
286
|
|
275
287
|
@staticmethod
|
276
288
|
def Subtract(matA, matB):
|
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,6 +6602,87 @@ class Topology():
|
|
6492
6602
|
return None
|
6493
6603
|
return topologic.Topology.IsSame(topologyA, topologyB)
|
6494
6604
|
|
6605
|
+
def IsVertexMatched(topologyA, topologyB, mantissa: int = 6, tolerance=0.0001, silent : bool = False):
|
6606
|
+
"""
|
6607
|
+
Returns True if the input topologies are vertex matched (have same number of vertices and all vertices are coincedent within a tolerance). Returns False otherwise.
|
6608
|
+
|
6609
|
+
Parameters
|
6610
|
+
----------
|
6611
|
+
topologyA : topologic_core.Topology
|
6612
|
+
The first input topology.
|
6613
|
+
topologyB : topologic_core.Topology
|
6614
|
+
The second input topology.
|
6615
|
+
mantissa : int , optional
|
6616
|
+
The desired length of the mantissa. The default is 6
|
6617
|
+
tolerance : float , optional
|
6618
|
+
The desired tolerance. The default is 0.0001.
|
6619
|
+
silent : bool , optional
|
6620
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6621
|
+
|
6622
|
+
Returns
|
6623
|
+
-------
|
6624
|
+
bool
|
6625
|
+
True of the input topologies are vertex matched. False otherwise.
|
6626
|
+
|
6627
|
+
"""
|
6628
|
+
from topologicpy.Vertex import Vertex
|
6629
|
+
|
6630
|
+
def coordinates_match(list1, list2, tolerance):
|
6631
|
+
"""
|
6632
|
+
Checks if all coordinates in list1 have a corresponding coordinate in list2
|
6633
|
+
within a specified tolerance, with each match being unique.
|
6634
|
+
|
6635
|
+
Parameters
|
6636
|
+
----------
|
6637
|
+
list1 : list of list of float
|
6638
|
+
The first list of coordinates, where each coordinate is [x, y, z].
|
6639
|
+
list2 : list of list of float
|
6640
|
+
The second list of coordinates, where each coordinate is [x, y, z].
|
6641
|
+
tolerance : float
|
6642
|
+
The maximum distance within which two coordinates are considered matching.
|
6643
|
+
|
6644
|
+
Returns
|
6645
|
+
-------
|
6646
|
+
bool
|
6647
|
+
True if all coordinates in list1 have a corresponding coordinate in list2
|
6648
|
+
within the tolerance. False otherwise.
|
6649
|
+
"""
|
6650
|
+
def distance(coord1, coord2):
|
6651
|
+
"""Calculate the Euclidean distance between two coordinates."""
|
6652
|
+
return np.linalg.norm(np.array(coord1) - np.array(coord2))
|
6653
|
+
|
6654
|
+
# Copy list2 to keep track of unvisited coordinates
|
6655
|
+
unmatched = list2.copy()
|
6656
|
+
|
6657
|
+
for coord1 in list1:
|
6658
|
+
match_found = False
|
6659
|
+
for i, coord2 in enumerate(unmatched):
|
6660
|
+
if distance(coord1, coord2) <= tolerance:
|
6661
|
+
# Mark the coordinate as visited by removing it
|
6662
|
+
unmatched.pop(i)
|
6663
|
+
match_found = True
|
6664
|
+
break
|
6665
|
+
if not match_found:
|
6666
|
+
return False
|
6667
|
+
return True
|
6668
|
+
|
6669
|
+
if not Topology.IsInstance(topologyA, "topology"):
|
6670
|
+
if not silent:
|
6671
|
+
print("Topology.IsVertexMatched - Error: The input topologyA parameter is not a valid topology. Returning None.")
|
6672
|
+
return None
|
6673
|
+
if not Topology.IsInstance(topologyB, "topology"):
|
6674
|
+
if not silent:
|
6675
|
+
print("Topology.IsVertexMatched - Error: The input topologyB parameter is not a valid topology. Returning None.")
|
6676
|
+
return None
|
6677
|
+
|
6678
|
+
vertices_a = Topology.Vertices(topologyA)
|
6679
|
+
vertices_b = Topology.Vertices(topologyB)
|
6680
|
+
if not len(vertices_a) == len(vertices_b):
|
6681
|
+
return False
|
6682
|
+
coords_a = [Vertex.Coordinates(v, mantissa=mantissa) for v in vertices_a]
|
6683
|
+
coords_b = [Vertex.Coordinates(v, mantissa=mantissa) for v in vertices_b]
|
6684
|
+
return coordinates_match(coords_a, coords_b, tolerance=tolerance)
|
6685
|
+
|
6495
6686
|
@staticmethod
|
6496
6687
|
def MergeAll(topologies, tolerance=0.0001):
|
6497
6688
|
"""
|
@@ -6752,7 +6943,7 @@ class Topology():
|
|
6752
6943
|
originB : topologic_core.Vertex , optional
|
6753
6944
|
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.
|
6754
6945
|
mantissa : int , optional
|
6755
|
-
The desired length of the mantissa. The default is 6
|
6946
|
+
The desired length of the mantissa. The default is 6.
|
6756
6947
|
|
6757
6948
|
Returns
|
6758
6949
|
-------
|
@@ -6779,6 +6970,90 @@ class Topology():
|
|
6779
6970
|
newTopology = None
|
6780
6971
|
return newTopology
|
6781
6972
|
|
6973
|
+
@staticmethod
|
6974
|
+
def PrincipalAxes(topology, n: int = 10, mantissa: int = 6, silent: bool = False):
|
6975
|
+
"""
|
6976
|
+
Returns the prinicipal axes (vectors) of the input topology.
|
6977
|
+
Please note that this is not a perfect algorithm and it can get confused based on the geometry of the input.
|
6978
|
+
Also, please note that there is no guarantee that three returned vectors match your expectation for an x,y,z axis order.
|
6979
|
+
|
6980
|
+
Parameters
|
6981
|
+
----------
|
6982
|
+
topology : topologic_core.Topology
|
6983
|
+
The input topology.
|
6984
|
+
n : int , optional
|
6985
|
+
The number of segments to use to increase the number of points on each face. The default is 10.
|
6986
|
+
mantissa : int , optional
|
6987
|
+
The desired length of the mantissa. The default is 6.
|
6988
|
+
silent : bool , optional
|
6989
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6990
|
+
|
6991
|
+
Returns
|
6992
|
+
-------
|
6993
|
+
list
|
6994
|
+
The list of x, y, and z vectors representing the principal axes of the topology.
|
6995
|
+
|
6996
|
+
"""
|
6997
|
+
from topologicpy.Vertex import Vertex
|
6998
|
+
from topologicpy.CellComplex import CellComplex
|
6999
|
+
from topologicpy.Topology import Topology
|
7000
|
+
from topologicpy.Grid import Grid
|
7001
|
+
from topologicpy.Vector import Vector
|
7002
|
+
import numpy as np
|
7003
|
+
|
7004
|
+
def generate_floats(n):
|
7005
|
+
if n < 2:
|
7006
|
+
return [0.0] if n == 1 else []
|
7007
|
+
return [i / (n - 1) for i in range(n)]
|
7008
|
+
|
7009
|
+
|
7010
|
+
if not Topology.IsInstance(topology, "topology"):
|
7011
|
+
if not silent:
|
7012
|
+
print("Topology.CanonicalMatrix - Error: The input topology parameter is not a valid topology. Returning None.")
|
7013
|
+
return None
|
7014
|
+
|
7015
|
+
faces = Topology.Faces(topology)
|
7016
|
+
if len(faces) == 0:
|
7017
|
+
if not silent:
|
7018
|
+
print("Topology.CanonicalMatrix - Error: The input topology parameter does not contain any faces. Returning None.")
|
7019
|
+
return None
|
7020
|
+
|
7021
|
+
# Step 1: Derive a copy topology to work with.
|
7022
|
+
if Topology.IsInstance(topology, "CellComplex"):
|
7023
|
+
top = CellComplex.ExternalBoundary(topology)
|
7024
|
+
else:
|
7025
|
+
top = Topology.Copy(topology)
|
7026
|
+
|
7027
|
+
# Step 2: Increase the number of vertices by adding a grid of points on each face.
|
7028
|
+
faces = Topology.Faces(top)
|
7029
|
+
vertices = Topology.Vertices(top)
|
7030
|
+
r = generate_floats(n)
|
7031
|
+
for face in faces:
|
7032
|
+
vertices += Topology.Vertices(Grid.VerticesByParameters(face=face, uRange=r, vRange=r, clip=True))
|
7033
|
+
points = np.array([[Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)] for v in vertices])
|
7034
|
+
|
7035
|
+
# Step 3: Align orientation using PCA
|
7036
|
+
# Compute PCA
|
7037
|
+
mean = points.mean(axis=0)
|
7038
|
+
centered_points = points - mean
|
7039
|
+
covariance_matrix = np.cov(centered_points.T)
|
7040
|
+
eigenvalues, eigenvectors = np.linalg.eigh(covariance_matrix)
|
7041
|
+
|
7042
|
+
# Sort eigenvectors by eigenvalues (largest first)
|
7043
|
+
sorted_indices = np.argsort(-eigenvalues)
|
7044
|
+
eigenvectors = eigenvectors[:, sorted_indices]
|
7045
|
+
|
7046
|
+
# Enforce consistent orientation by flipping eigenvectors
|
7047
|
+
for i in range(3): # Ensure each eigenvector points in a positive direction
|
7048
|
+
if np.dot(eigenvectors[:, i], [1, 0, 0]) < 0:
|
7049
|
+
eigenvectors[:, i] *= -1
|
7050
|
+
|
7051
|
+
# Retrieve and return the principal axes
|
7052
|
+
x_axis = Vector.ByCoordinates(*eigenvectors[:, 0])
|
7053
|
+
y_axis = Vector.ByCoordinates(*eigenvectors[:, 1])
|
7054
|
+
z_axis = Vector.ByCoordinates(*eigenvectors[:, 2])
|
7055
|
+
return x_axis, y_axis, z_axis
|
7056
|
+
|
6782
7057
|
@staticmethod
|
6783
7058
|
def RemoveCollinearEdges(topology, angTolerance: float = 0.1, tolerance: float = 0.0001, silent: bool = False):
|
6784
7059
|
"""
|
@@ -9229,9 +9504,9 @@ class Topology():
|
|
9229
9504
|
kRotation31 = matrix[2][0]
|
9230
9505
|
kRotation32 = matrix[2][1]
|
9231
9506
|
kRotation33 = matrix[2][2]
|
9232
|
-
kTranslationX = matrix[
|
9233
|
-
kTranslationY = matrix[
|
9234
|
-
kTranslationZ = matrix[
|
9507
|
+
kTranslationX = matrix[0][3]
|
9508
|
+
kTranslationY = matrix[1][3]
|
9509
|
+
kTranslationZ = matrix[2][3]
|
9235
9510
|
|
9236
9511
|
return_topology = topologic.TopologyUtility.Transform(topology, kTranslationX, kTranslationY, kTranslationZ, kRotation11, kRotation12, kRotation13, kRotation21, kRotation22, kRotation23, kRotation31, kRotation32, kRotation33)
|
9237
9512
|
|