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/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,-s, 0],
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 [[ c, 0, s, 0],
87
- [ 0, 1, 0, 0],
63
+ return [[c, 0, s, 0],
64
+ [0, 1, 0, 0],
88
65
  [-s, 0, c, 0],
89
- [ 0, 0, 0, 1]]
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,-s, 0, 0],
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,0],
163
- [0,1,0,0],
164
- [0,0,1,0],
165
- [translateX,translateY,translateZ,1]]
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 Multiply(matA, matB):
208
+ def Invert(matA, silent: bool = False):
232
209
  """
233
- Multiplies the two input matrices. When transforming an object, the first input matrix is applied first
234
- then the second input matrix.
235
-
210
+ Inverts the input matrix.
211
+
236
212
  Parameters
237
213
  ----------
238
- matA : list
239
- The first input matrix.
240
- matB : list
241
- The second input matrix.
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 matrix resulting from the multiplication of the two input matrices.
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
- if not isinstance(matB, list):
252
- return None
253
- nr = len(matA)
254
- nc = len(matA[0])
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
- if not isinstance(matB, list):
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
- # iterate through rows of X
266
- for i in range(len(matA)):
267
- # iterate through columns of Y
268
- tempRow = []
269
- for j in range(len(matB[0])):
270
- # iterate through rows of Y
271
- for k in range(len(matB)):
272
- matC[i][j] += matA[i][k] * matB[k][j]
273
- return matC
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[3][0]
9233
- kTranslationY = matrix[3][1]
9234
- kTranslationZ = matrix[3][2]
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