chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / fabmetheus_utilities / geometry / geometry_utilities / matrix.py
1 """
2 Boolean geometry four by four matrix.
3
4 """
5
6 from __future__ import absolute_import
7 #Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
8 import __init__
9
10 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
11 from fabmetheus_utilities.vector3 import Vector3
12 from fabmetheus_utilities import archive
13 from fabmetheus_utilities import euclidean
14 from fabmetheus_utilities import xml_simple_writer
15 import cStringIO
16 import math
17
18
19 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
20 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
21 __date__ = '$Date: 2008/02/05 $'
22 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
23
24
25 globalExecutionOrder = 300
26
27
28 def addVertexes(geometryOutput, vertexes):
29         'Add the vertexes.'
30         if geometryOutput.__class__ == list:
31                 for element in geometryOutput:
32                         addVertexes(element, vertexes)
33                 return
34         if geometryOutput.__class__ == dict:
35                 for geometryOutputKey in geometryOutput.keys():
36                         if geometryOutputKey == 'vertex':
37                                 vertexes += geometryOutput[geometryOutputKey]
38                         else:
39                                 addVertexes(geometryOutput[geometryOutputKey], vertexes)
40
41 def getBranchMatrix(elementNode):
42         'Get matrix starting from the object if it exists, otherwise get a matrix starting from stratch.'
43         branchMatrix = Matrix()
44         matrixChildElement = elementNode.getFirstChildByLocalName('matrix')
45         if matrixChildElement != None:
46                 branchMatrix = branchMatrix.getFromElementNode(matrixChildElement, '')
47         branchMatrix = branchMatrix.getFromElementNode(elementNode, 'matrix.')
48         if elementNode.xmlObject == None:
49                 return branchMatrix
50         elementNodeMatrix = elementNode.xmlObject.getMatrix4X4()
51         if elementNodeMatrix == None:
52                 return branchMatrix
53         return elementNodeMatrix.getOtherTimesSelf(branchMatrix.tetragrid)
54
55 def getBranchMatrixSetElementNode(elementNode):
56         'Get matrix starting from the object if it exists, otherwise get a matrix starting from stratch.'
57         branchMatrix = getBranchMatrix(elementNode)
58         setElementNodeDictionaryMatrix(elementNode, branchMatrix)
59         return branchMatrix
60
61 def getCumulativeVector3Remove(defaultVector3, elementNode, prefix):
62         'Get cumulative vector3 and delete the prefixed attributes.'
63         if prefix == '':
64                 defaultVector3.x = evaluate.getEvaluatedFloat(defaultVector3.x, elementNode, 'x')
65                 defaultVector3.y = evaluate.getEvaluatedFloat(defaultVector3.y, elementNode, 'y')
66                 defaultVector3.z = evaluate.getEvaluatedFloat(defaultVector3.z, elementNode, 'z')
67                 euclidean.removeElementsFromDictionary(elementNode.attributes, ['x', 'y', 'z'])
68                 prefix = 'cartesian'
69         defaultVector3 = evaluate.getVector3ByPrefix(defaultVector3, elementNode, prefix)
70         euclidean.removePrefixFromDictionary(elementNode.attributes, prefix)
71         return defaultVector3
72
73 def getDiagonalSwitchedTetragrid(angleDegrees, diagonals):
74         'Get the diagonals and switched matrix by degrees.'
75         return getDiagonalSwitchedTetragridByRadians(math.radians(angleDegrees), diagonals)
76
77 def getDiagonalSwitchedTetragridByPolar(diagonals, unitPolar):
78         'Get the diagonals and switched matrix by unitPolar.'
79         diagonalSwitchedTetragrid = getIdentityTetragrid()
80         for diagonal in diagonals:
81                 diagonalSwitchedTetragrid[diagonal][diagonal] = unitPolar.real
82         diagonalSwitchedTetragrid[diagonals[0]][diagonals[1]] = -unitPolar.imag
83         diagonalSwitchedTetragrid[diagonals[1]][diagonals[0]] = unitPolar.imag
84         return diagonalSwitchedTetragrid
85
86 def getDiagonalSwitchedTetragridByRadians(angleRadians, diagonals):
87         'Get the diagonals and switched matrix by radians.'
88         return getDiagonalSwitchedTetragridByPolar(diagonals, euclidean.getWiddershinsUnitPolar(angleRadians))
89
90 def getIdentityTetragrid(tetragrid=None):
91         'Get four by four matrix with diagonal elements set to one.'
92         if tetragrid == None:
93                 return [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]]
94         return tetragrid
95
96 def getIsIdentityTetragrid(tetragrid):
97         'Determine if the tetragrid is the identity tetragrid.'
98         for column in xrange(4):
99                 for row in xrange(4):
100                         if column == row:
101                                 if tetragrid[column][row] != 1.0:
102                                         return False
103                         elif tetragrid[column][row] != 0.0:
104                                 return False
105         return True
106
107 def getIsIdentityTetragridOrNone(tetragrid):
108         'Determine if the tetragrid is None or if it is the identity tetragrid.'
109         if tetragrid == None:
110                 return True
111         return getIsIdentityTetragrid(tetragrid)
112
113 def getKeyA(row, column, prefix=''):
114         'Get the a format key string from row & column, counting from zero.'
115         return '%sa%s%s' % (prefix, row, column)
116
117 def getKeyM(row, column, prefix=''):
118         'Get the m format key string from row & column, counting from one.'
119         return '%sm%s%s' % (prefix, row + 1, column + 1)
120
121 def getKeysA(prefix=''):
122         'Get the matrix keys, counting from zero.'
123         keysA = []
124         for row in xrange(4):
125                 for column in xrange(4):
126                         key = getKeyA(row, column, prefix)
127                         keysA.append(key)
128         return keysA
129
130 def getKeysM(prefix=''):
131         'Get the matrix keys, counting from one.'
132         keysM = []
133         for row in xrange(4):
134                 for column in xrange(4):
135                         key = getKeyM(row, column, prefix)
136                         keysM.append(key)
137         return keysM
138
139 def getRemovedFloat(defaultFloat, elementNode, key, prefix):
140         'Get the float by the key and the prefix.'
141         prefixKey = prefix + key
142         if prefixKey in elementNode.attributes:
143                 floatValue = evaluate.getEvaluatedFloat(None, elementNode, prefixKey)
144                 if floatValue == None:
145                         print('Warning, evaluated value in getRemovedFloatByKeys in matrix is None for key:')
146                         print(prefixKey)
147                         print('for elementNode dictionary value:')
148                         print(elementNode.attributes[prefixKey])
149                         print('for elementNode dictionary:')
150                         print(elementNode.attributes)
151                 else:
152                         defaultFloat = floatValue
153                 del elementNode.attributes[prefixKey]
154         return defaultFloat
155
156 def getRemovedFloatByKeys(defaultFloat, elementNode, keys, prefix):
157         'Get the float by the keys and the prefix.'
158         for key in keys:
159                 defaultFloat = getRemovedFloat(defaultFloat, elementNode, key, prefix)
160         return defaultFloat
161
162 def getRotateAroundAxisTetragrid(elementNode, prefix):
163         'Get rotate around axis tetragrid and delete the axis and angle attributes.'
164         angle = getRemovedFloatByKeys(0.0, elementNode, ['angle', 'counterclockwise'], prefix)
165         angle -= getRemovedFloat(0.0, elementNode, 'clockwise', prefix)
166         if angle == 0.0:
167                 return None
168         angleRadians = math.radians(angle)
169         axis = getCumulativeVector3Remove(Vector3(), elementNode, prefix + 'axis')
170         axisLength = abs(axis)
171         if axisLength <= 0.0:
172                 print('Warning, axisLength was zero in getRotateAroundAxisTetragrid in matrix so nothing will be done for:')
173                 print(elementNode)
174                 return None
175         axis /= axisLength
176         tetragrid = getIdentityTetragrid()
177         cosAngle = math.cos(angleRadians)
178         sinAngle = math.sin(angleRadians)
179         oneMinusCos = 1.0 - math.cos(angleRadians)
180         xx = axis.x * axis.x
181         xy = axis.x * axis.y
182         xz = axis.x * axis.z
183         yy = axis.y * axis.y
184         yz = axis.y * axis.z
185         zz = axis.z * axis.z
186         tetragrid[0] = [cosAngle + xx * oneMinusCos, xy * oneMinusCos - axis.z * sinAngle, xz * oneMinusCos + axis.y * sinAngle, 0.0]
187         tetragrid[1] = [xy * oneMinusCos + axis.z * sinAngle, cosAngle + yy * oneMinusCos, yz * oneMinusCos - axis.x * sinAngle, 0.0]
188         tetragrid[2] = [xz * oneMinusCos - axis.y * sinAngle, yz * oneMinusCos + axis.x * sinAngle, cosAngle + zz * oneMinusCos, 0.0]
189         return tetragrid
190
191 def getRotateTetragrid(elementNode, prefix):
192         'Get rotate tetragrid and delete the rotate attributes.'
193         # http://en.wikipedia.org/wiki/Rotation_matrix
194         rotateMatrix = Matrix()
195         rotateMatrix.tetragrid = getRotateAroundAxisTetragrid(elementNode, prefix)
196         zAngle = getRemovedFloatByKeys(0.0, elementNode, ['axisclockwisez', 'observerclockwisez', 'z'], prefix)
197         zAngle -= getRemovedFloatByKeys(0.0, elementNode, ['axiscounterclockwisez', 'observercounterclockwisez'], prefix)
198         if zAngle != 0.0:
199                 rotateMatrix.tetragrid = getTetragridTimesOther(getDiagonalSwitchedTetragrid(-zAngle, [0, 1]), rotateMatrix.tetragrid)
200         xAngle = getRemovedFloatByKeys(0.0, elementNode, ['axisclockwisex', 'observerclockwisex', 'x'], prefix)
201         xAngle -= getRemovedFloatByKeys(0.0, elementNode, ['axiscounterclockwisex', 'observercounterclockwisex'], prefix)
202         if xAngle != 0.0:
203                 rotateMatrix.tetragrid = getTetragridTimesOther(getDiagonalSwitchedTetragrid(-xAngle, [1, 2]), rotateMatrix.tetragrid)
204         yAngle = getRemovedFloatByKeys(0.0, elementNode, ['axiscounterclockwisey', 'observerclockwisey', 'y'], prefix)
205         yAngle -= getRemovedFloatByKeys(0.0, elementNode, ['axisclockwisey', 'observercounterclockwisey'], prefix)
206         if yAngle != 0.0:
207                 rotateMatrix.tetragrid = getTetragridTimesOther(getDiagonalSwitchedTetragrid(yAngle, [0, 2]), rotateMatrix.tetragrid)
208         return rotateMatrix.tetragrid
209
210 def getScaleTetragrid(elementNode, prefix):
211         'Get scale matrix and delete the scale attributes.'
212         scaleDefaultVector3 = Vector3(1.0, 1.0, 1.0)
213         scale = getCumulativeVector3Remove(scaleDefaultVector3.copy(), elementNode, prefix)
214         if scale == scaleDefaultVector3:
215                 return None
216         return [[scale.x, 0.0, 0.0, 0.0], [0.0, scale.y, 0.0, 0.0], [0.0, 0.0, scale.z, 0.0], [0.0, 0.0, 0.0, 1.0]]
217
218 def getTetragridA(elementNode, prefix, tetragrid):
219         'Get the tetragrid from the elementNode letter a values.'
220         keysA = getKeysA(prefix)
221         evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, keysA)
222         if len(evaluatedDictionary.keys()) < 1:
223                 return tetragrid
224         for row in xrange(4):
225                 for column in xrange(4):
226                         key = getKeyA(row, column, prefix)
227                         if key in evaluatedDictionary:
228                                 value = evaluatedDictionary[key]
229                                 if value == None or value == 'None':
230                                         print('Warning, value in getTetragridA in matrix is None for key for dictionary:')
231                                         print(key)
232                                         print(evaluatedDictionary)
233                                 else:
234                                         tetragrid = getIdentityTetragrid(tetragrid)
235                                         tetragrid[row][column] = float(value)
236         euclidean.removeElementsFromDictionary(elementNode.attributes, keysA)
237         return tetragrid
238
239 def getTetragridC(elementNode, prefix, tetragrid):
240         'Get the matrix Tetragrid from the elementNode letter c values.'
241         columnKeys = 'Pc1 Pc2 Pc3 Pc4'.replace('P', prefix).split()
242         evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, columnKeys)
243         if len(evaluatedDictionary.keys()) < 1:
244                 return tetragrid
245         for columnKeyIndex, columnKey in enumerate(columnKeys):
246                 if columnKey in evaluatedDictionary:
247                         value = evaluatedDictionary[columnKey]
248                         if value == None or value == 'None':
249                                 print('Warning, value in getTetragridC in matrix is None for columnKey for dictionary:')
250                                 print(columnKey)
251                                 print(evaluatedDictionary)
252                         else:
253                                 tetragrid = getIdentityTetragrid(tetragrid)
254                                 for elementIndex, element in enumerate(value):
255                                         tetragrid[elementIndex][columnKeyIndex] = element
256         euclidean.removeElementsFromDictionary(elementNode.attributes, columnKeys)
257         return tetragrid
258
259 def getTetragridCopy(tetragrid):
260         'Get tetragrid copy.'
261         if tetragrid == None:
262                 return None
263         tetragridCopy = []
264         for tetragridRow in tetragrid:
265                 tetragridCopy.append(tetragridRow[:])
266         return tetragridCopy
267
268 def getTetragridM(elementNode, prefix, tetragrid):
269         'Get the tetragrid from the elementNode letter m values.'
270         keysM = getKeysM(prefix)
271         evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, keysM)
272         if len(evaluatedDictionary.keys()) < 1:
273                 return tetragrid
274         for row in xrange(4):
275                 for column in xrange(4):
276                         key = getKeyM(row, column, prefix)
277                         if key in evaluatedDictionary:
278                                 value = evaluatedDictionary[key]
279                                 if value == None or value == 'None':
280                                         print('Warning, value in getTetragridM in matrix is None for key for dictionary:')
281                                         print(key)
282                                         print(evaluatedDictionary)
283                                 else:
284                                         tetragrid = getIdentityTetragrid(tetragrid)
285                                         tetragrid[row][column] = float(value)
286         euclidean.removeElementsFromDictionary(elementNode.attributes, keysM)
287         return tetragrid
288
289 def getTetragridMatrix(elementNode, prefix, tetragrid):
290         'Get the tetragrid from the elementNode matrix value.'
291         matrixKey = prefix + 'matrix'
292         evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, [matrixKey])
293         if len(evaluatedDictionary.keys()) < 1:
294                 return tetragrid
295         value = evaluatedDictionary[matrixKey]
296         if value == None or value == 'None':
297                 print('Warning, value in getTetragridMatrix in matrix is None for matrixKey for dictionary:')
298                 print(matrixKey)
299                 print(evaluatedDictionary)
300         else:
301                 tetragrid = getIdentityTetragrid(tetragrid)
302                 for rowIndex, row in enumerate(value):
303                         for elementIndex, element in enumerate(row):
304                                 tetragrid[rowIndex][elementIndex] = element
305         euclidean.removeElementsFromDictionary(elementNode.attributes, [matrixKey])
306         return tetragrid
307
308 def getTetragridR(elementNode, prefix, tetragrid):
309         'Get the tetragrid from the elementNode letter r values.'
310         rowKeys = 'Pr1 Pr2 Pr3 Pr4'.replace('P', prefix).split()
311         evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, rowKeys)
312         if len(evaluatedDictionary.keys()) < 1:
313                 return tetragrid
314         for rowKeyIndex, rowKey in enumerate(rowKeys):
315                 if rowKey in evaluatedDictionary:
316                         value = evaluatedDictionary[rowKey]
317                         if value == None or value == 'None':
318                                 print('Warning, value in getTetragridR in matrix is None for rowKey for dictionary:')
319                                 print(rowKey)
320                                 print(evaluatedDictionary)
321                         else:
322                                 tetragrid = getIdentityTetragrid(tetragrid)
323                                 for elementIndex, element in enumerate(value):
324                                         tetragrid[rowKeyIndex][elementIndex] = element
325         euclidean.removeElementsFromDictionary(elementNode.attributes, rowKeys)
326         return tetragrid
327
328 def getTetragridTimesOther(firstTetragrid, otherTetragrid ):
329         'Get this matrix multiplied by the other matrix.'
330         #A down, B right from http://en.wikipedia.org/wiki/Matrix_multiplication
331         if firstTetragrid == None:
332                 return otherTetragrid
333         if otherTetragrid == None:
334                 return firstTetragrid
335         tetragridTimesOther = []
336         for row in xrange(4):
337                 matrixRow = firstTetragrid[row]
338                 tetragridTimesOtherRow = []
339                 tetragridTimesOther.append(tetragridTimesOtherRow)
340                 for column in xrange(4):
341                         dotProduct = 0
342                         for elementIndex in xrange(4):
343                                 dotProduct += matrixRow[elementIndex] * otherTetragrid[elementIndex][column]
344                         tetragridTimesOtherRow.append(dotProduct)
345         return tetragridTimesOther
346
347 def getTransformedByList(floatList, point):
348         'Get the point transformed by the array.'
349         return floatList[0] * point.x + floatList[1] * point.y + floatList[2] * point.z + floatList[3]
350
351 def getTransformedVector3(tetragrid, vector3):
352         'Get the vector3 multiplied by a matrix.'
353         if getIsIdentityTetragridOrNone(tetragrid):
354                 return vector3.copy()
355         return getTransformedVector3Blindly(tetragrid, vector3)
356
357 def getTransformedVector3Blindly(tetragrid, vector3):
358         'Get the vector3 multiplied by a tetragrid without checking if the tetragrid exists.'
359         return Vector3(
360                 getTransformedByList(tetragrid[0], vector3),
361                 getTransformedByList(tetragrid[1], vector3),
362                 getTransformedByList(tetragrid[2], vector3))
363
364 def getTransformedVector3s(tetragrid, vector3s):
365         'Get the vector3s multiplied by a matrix.'
366         if getIsIdentityTetragridOrNone(tetragrid):
367                 return euclidean.getPathCopy(vector3s)
368         transformedVector3s = []
369         for vector3 in vector3s:
370                 transformedVector3s.append(getTransformedVector3Blindly(tetragrid, vector3))
371         return transformedVector3s
372
373 def getTransformTetragrid(elementNode, prefix):
374         'Get the tetragrid from the elementNode.'
375         tetragrid = getTetragridA(elementNode, prefix, None)
376         tetragrid = getTetragridC(elementNode, prefix, tetragrid)
377         tetragrid = getTetragridM(elementNode, prefix, tetragrid)
378         tetragrid = getTetragridMatrix(elementNode, prefix, tetragrid)
379         tetragrid = getTetragridR(elementNode, prefix, tetragrid)
380         return tetragrid
381
382 def getTranslateTetragrid(elementNode, prefix):
383         'Get translate matrix and delete the translate attributes.'
384         translation = getCumulativeVector3Remove(Vector3(), elementNode, prefix)
385         if translation.getIsDefault():
386                 return None
387         return getTranslateTetragridByTranslation(translation)
388
389 def getTranslateTetragridByTranslation(translation):
390         'Get translate tetragrid by translation.'
391         return [[1.0, 0.0, 0.0, translation.x], [0.0, 1.0, 0.0, translation.y], [0.0, 0.0, 1.0, translation.z], [0.0, 0.0, 0.0, 1.0]]
392
393 def getVertexes(geometryOutput):
394         'Get the vertexes.'
395         vertexes = []
396         addVertexes(geometryOutput, vertexes)
397         return vertexes
398
399 def setAttributesToMultipliedTetragrid(elementNode, tetragrid):
400         'Set the element attribute dictionary and element matrix to the matrix times the tetragrid.'
401         setElementNodeDictionaryMatrix(elementNode, getBranchMatrix(elementNode).getOtherTimesSelf(tetragrid))
402
403 def setElementNodeDictionaryMatrix(elementNode, matrix4X4):
404         'Set the element attribute dictionary or element matrix to the matrix.'
405         if elementNode.xmlObject == None:
406                 elementNode.attributes.update(matrix4X4.getAttributes('matrix.'))
407         else:
408                 elementNode.xmlObject.matrix4X4 = matrix4X4
409
410 def transformVector3Blindly(tetragrid, vector3):
411         'Transform the vector3 by a tetragrid without checking to see if it exists.'
412         x = getTransformedByList(tetragrid[0], vector3)
413         y = getTransformedByList(tetragrid[1], vector3)
414         z = getTransformedByList(tetragrid[2], vector3)
415         vector3.x = x
416         vector3.y = y
417         vector3.z = z
418
419 def transformVector3ByMatrix(tetragrid, vector3):
420         'Transform the vector3 by a matrix.'
421         if getIsIdentityTetragridOrNone(tetragrid):
422                 return
423         transformVector3Blindly(tetragrid, vector3)
424
425 def transformVector3sByMatrix(tetragrid, vector3s):
426         'Transform the vector3s by a matrix.'
427         if getIsIdentityTetragridOrNone(tetragrid):
428                 return
429         for vector3 in vector3s:
430                 transformVector3Blindly(tetragrid, vector3)
431
432
433 class Matrix:
434         'A four by four matrix.'
435         def __init__(self, tetragrid=None):
436                 'Add empty lists.'
437                 self.tetragrid = getTetragridCopy(tetragrid)
438
439         def __eq__(self, other):
440                 'Determine whether this matrix is identical to other one.'
441                 if other == None:
442                         return False
443                 if other.__class__ != self.__class__:
444                         return False
445                 return other.tetragrid == self.tetragrid
446
447         def __ne__(self, other):
448                 'Determine whether this vector is not identical to other one.'
449                 return not self.__eq__(other)
450
451         def __repr__(self):
452                 'Get the string representation of this four by four matrix.'
453                 output = cStringIO.StringIO()
454                 self.addXML(0, output)
455                 return output.getvalue()
456
457         def addXML(self, depth, output):
458                 'Add xml for this object.'
459                 attributes = self.getAttributes()
460                 if len(attributes) > 0:
461                         xml_simple_writer.addClosedXMLTag(attributes, depth, self.__class__.__name__.lower(), output)
462
463         def getAttributes(self, prefix=''):
464                 'Get the attributes from row column attribute strings, counting from one.'
465                 attributes = {}
466                 if self.tetragrid == None:
467                         return attributes
468                 for row in xrange(4):
469                         for column in xrange(4):
470                                 default = float(column == row)
471                                 value = self.tetragrid[row][column]
472                                 if abs( value - default ) > 0.00000000000001:
473                                         if abs(value) < 0.00000000000001:
474                                                 value = 0.0
475                                         attributes[prefix + getKeyM(row, column)] = value
476                 return attributes
477
478         def getFromElementNode(self, elementNode, prefix):
479                 'Get the values from row column attribute strings, counting from one.'
480                 attributes = elementNode.attributes
481                 if attributes == None:
482                         return self
483                 self.tetragrid = getTetragridTimesOther(getTransformTetragrid(elementNode, prefix), self.tetragrid)
484                 self.tetragrid = getTetragridTimesOther(getScaleTetragrid(elementNode, 'scale.'), self.tetragrid)
485                 self.tetragrid = getTetragridTimesOther(getRotateTetragrid(elementNode, 'rotate.'), self.tetragrid)
486                 self.tetragrid = getTetragridTimesOther(getTranslateTetragrid(elementNode, 'translate.'), self.tetragrid)
487                 return self
488
489         def getOtherTimesSelf(self, otherTetragrid):
490                 'Get this matrix reverse multiplied by the other matrix.'
491                 return Matrix(getTetragridTimesOther(otherTetragrid, self.tetragrid))
492
493         def getSelfTimesOther(self, otherTetragrid):
494                 'Get this matrix multiplied by the other matrix.'
495                 return Matrix(getTetragridTimesOther(self.tetragrid, otherTetragrid))