""" A wrap of Maya's Vector, Point, Color, Matrix, TransformationMatrix, Quaternion, EulerRotation types """ import sys import math, copy import operator, colorsys import pymel.util as util import pymel.api as _api from pymel.util.arrays import * from pymel.util.arrays import _toCompOrArrayInstance import pymel.internal.factories as _factories # in python2.6/maya2010 'as' becomes a keyword. # TODO: add a version check: if sys.version_info >= (2,6): AS_UNITS = 'asUnits' else: AS_UNITS = 'as' # patch some Maya api classes that miss __iter__ to make them iterable / convertible to list def _patchMVector() : def __len__(self): """ Number of components in the Maya api Vector, ie 3 """ return 3 type.__setattr__(_api.MVector, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api Vector """ for i in xrange(len(self)) : yield _api.MVector.__getitem__(self, i) type.__setattr__(_api.MVector, '__iter__', __iter__) def _patchMFloatVector() : def __len__(self): """ Number of components in the Maya api FloatVector, ie 3 """ return 3 type.__setattr__(_api.MFloatVector, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api FloatVector """ for i in xrange(len(self)) : yield _api.MFloatVector.__getitem__(self, i) type.__setattr__(_api.MFloatVector, '__iter__', __iter__) def _patchMPoint() : def __len__(self): """ Number of components in the Maya api Point, ie 4 """ return 4 type.__setattr__(_api.MPoint, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api Point """ for i in xrange(len(self)) : yield _api.MPoint.__getitem__(self, i) type.__setattr__(_api.MPoint, '__iter__', __iter__) def _patchMFloatPoint() : def __len__(self): """ Number of components in the Maya api FloatPoint, ie 4 """ return 4 type.__setattr__(_api.MFloatPoint, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api FloatPoint """ for i in xrange(len(self)) : yield _api.MFloatPoint.__getitem__(self, i) type.__setattr__(_api.MFloatPoint, '__iter__', __iter__) def _patchMColor() : def __len__(self): """ Number of components in the Maya api Color, ie 4 """ return 4 type.__setattr__(_api.MColor, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api Color """ for i in xrange(len(self)) : yield _api.MColor.__getitem__(self, i) type.__setattr__(_api.MColor, '__iter__', __iter__) def _patchMMatrix() : def __len__(self): """ Number of rows in the Maya api Matrix, ie 4. Not to be confused with the number of components (16) given by the size method """ return 4 type.__setattr__(_api.MMatrix, '__len__', __len__) def __iter__(self): """ Iterates on all 4 rows of a Maya api Matrix """ for r in xrange(4) : yield Array([_api.MScriptUtil.getDoubleArrayItem(_api.MMatrix.__getitem__(self, r), c) for c in xrange(4)]) type.__setattr__(_api.MMatrix, '__iter__', __iter__) def _patchMFloatMatrix() : def __len__(self): """ Number of rows in the Maya api FloatMatrix, ie 4. Not to be confused with the number of components (16) given by the size method """ return 4 type.__setattr__(_api.MFloatMatrix, '__len__', __len__) def __iter__(self): """ Iterates on all 4 rows of a Maya api FloatMatrix """ for r in xrange(4) : yield Array([_api.MScriptUtil.getFloatArrayItem(_api.MFloatMatrix.__getitem__(self, r), c) for c in xrange(4)]) type.__setattr__(_api.MFloatMatrix, '__iter__', __iter__) def _patchMTransformationMatrix() : def __len__(self): """ Number of rows in the Maya api Matrix, ie 4. Not to be confused with the number of components (16) given by the size method """ return 4 type.__setattr__(_api.MTransformationMatrix, '__len__', __len__) def __iter__(self): """ Iterates on all 4 rows of a Maya api TransformationMatrix """ return self.asMatrix().__iter__() type.__setattr__(_api.MTransformationMatrix, '__iter__', __iter__) def _patchMQuaternion() : def __len__(self): """ Number of components in the Maya api Quaternion, ie 4 """ return 4 type.__setattr__(_api.MQuaternion, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api Quaternion """ for i in xrange(len(self)) : yield _api.MQuaternion.__getitem__(self, i) type.__setattr__(_api.MQuaternion, '__iter__', __iter__) def _patchMEulerRotation() : def __len__(self): """ Number of components in the Maya api EulerRotation, ie 3 """ return 3 type.__setattr__(_api.MEulerRotation, '__len__', __len__) def __iter__(self): """ Iterates on all components of a Maya api EulerRotation """ for i in xrange(len(self)) : yield _api.MEulerRotation.__getitem__(self, i) type.__setattr__(_api.MEulerRotation, '__iter__', __iter__) _patchMVector() _patchMFloatVector() _patchMPoint() _patchMFloatPoint() _patchMColor() _patchMMatrix() _patchMFloatMatrix() _patchMTransformationMatrix() _patchMQuaternion() _patchMEulerRotation() # the meta class of metaMayaWrapper class MetaMayaArrayTypeWrapper(_factories.MetaMayaTypeWrapper) : """ A metaclass to wrap Maya array type classes such as Vector, Matrix """ def __new__(mcl, classname, bases, classdict): """ Create a new wrapping class for a Maya api type, such as Vector or Matrix """ if 'shape' in classdict : # fixed shape means also fixed ndim and size shape = classdict['shape'] ndim = len(shape) size = reduce(operator.mul, shape, 1) if 'ndim' not in classdict : classdict['ndim'] = ndim elif classdict['ndim'] != ndim : raise ValueError, "class %s shape definition %s and number of dimensions definition %s do not match" % (classname, shape, ndim) if 'size' not in classdict : classdict['size'] = size elif classdict['size'] != size : raise ValueError, "class %s shape definition %s and size definition %s do not match" % (classname, shape, size) # create the new class newcls = super(MetaMayaArrayTypeWrapper, mcl).__new__(mcl, classname, bases, classdict) try : apicls = newcls.apicls except : apicls = None try : shape = newcls.shape except : shape = None try : cnames = newcls.cnames except : cnames = () if shape is not None : # fixed shape means also fixed ndim and size ndim = len(shape) size = reduce(operator.mul, shape, 1) if cnames : # definition for component names type.__setattr__(newcls, 'cnames', cnames ) subsizes = [reduce(operator.mul, shape[i+1:], 1) for i in xrange(ndim)] for index, compname in enumerate(cnames) : coords = [] for i in xrange(ndim) : c = index//subsizes[i] index -= c*subsizes[i] coords.append(c) if len(coords) == 1 : coords = coords[0] else : coords = tuple(coords) # def _get(self): # return self.__getitem__(coords) # _get.__name__ = '_get_' + compname # # # FIXME : the set property does not do anything in python 2.4 !!! It doesn't even get called. # # def _set(self, val): # self.__setitem__(coords, val) # # _set.__name__ = '_set_' + compname # # p = property( _get, _set, None, 'set and get %s component' % compname ) cmd = "property( lambda self: self.__getitem__(%s) , lambda self, val: self.__setitem__(%s,val) )" % (coords, coords) p = eval(cmd) if compname not in classdict : type.__setattr__(newcls, compname, p) else : raise AttributeError, "component name %s clashes with class method %r" % (compname, classdict[compname]) elif cnames : raise ValueError, "can only define component names for classes with a fixed shape/size" # constants for shape, ndim, size if shape is not None : type.__setattr__(newcls, 'shape', shape) if ndim is not None : type.__setattr__(newcls, 'ndim', ndim) if size is not None : type.__setattr__(newcls, 'size', size) #__slots__ = ['_data', '_shape', '_size'] # add component names to read-only list readonly = newcls.__readonly__ if hasattr(newcls, 'shape') : readonly['shape'] = None if hasattr(newcls, 'ndim') : readonly['ndim'] = None if hasattr(newcls, 'size') : readonly['size'] = None if 'cnames' not in readonly : readonly['cnames'] = None type.__setattr__(newcls, '__readonly__', readonly) # print "created class", newcls # print "bases", newcls.__bases__ # print "readonly", newcls.__readonly__ # print "slots", newcls.__slots__ return newcls # generic math function that can operate on Arrays herited from arrays # (min, max, sum, prod...) # Functions that work on vectors will now be inherited from Array and properly defer # to the class methods class Vector(VectorN) : """ A 3 dimensional vector class that wraps Maya's api Vector class >>> from pymel.all import * >>> import pymel.core.datatypes as dt >>> >>> v = dt.Vector(1, 2, 3) >>> w = dt.Vector(x=1, z=2) >>> z = dt.Vector( dt.Vector.xAxis, z=1) >>> v = dt.Vector(1, 2, 3, unit='meters') >>> print v [1.0, 2.0, 3.0] """ __metaclass__ = MetaMayaArrayTypeWrapper __slots__ = () # class specific info apicls = _api.MVector cnames = ('x', 'y', 'z') shape = (3,) unit = None def __new__(cls, *args, **kwargs): shape = kwargs.get('shape', None) ndim = kwargs.get('ndim', None) size = kwargs.get('size', None) # will default to class constant shape = (3,), so it's just an error check to catch invalid shapes, # as no other option is actually possible on Vector, but this method could be used to allow wrapping # of Maya array classes that can have a variable number of elements shape, ndim, size = cls._expandshape(shape, ndim, size) new = cls.apicls.__new__(cls) cls.apicls.__init__(new) return new def __init__(self, *args, **kwargs): """ __init__ method, valid for Vector, Point and Color classes """ cls = self.__class__ if args : # allow both forms for arguments if len(args)==1 and hasattr(args[0], '__iter__') : args = args[0] # shortcut when a direct api init is possible try : self.assign(args) except : # special exception to the rule that you cannot drop data in Arrays __init__ # to allow all conversion from Vector derived classes (MPoint, MColor) to a base class # special case for MPoint to cartesianize if necessary # note : we may want to premultiply MColor by the alpha in a similar way if isinstance(args, _api.MPoint) and args.w != 1.0 : args = copy.deepcopy(args).cartesianize() if isinstance(args, _api.MColor) and args.a != 1.0 : # note : we may want to premultiply Color by the alpha in a similar way pass if isinstance(args, _api.MVector) or isinstance(args, _api.MPoint) or isinstance(args, _api.MColor) : args = tuple(args) if len(args) > len(self) : args = args[slice(self.shape[0])] super(Vector, self).__init__(*args) if hasattr(cls, 'cnames') and len(set(cls.cnames) & set(kwargs)) : # can also use the form <componentname>=<number> l = list(self.flat) setcomp = False for i, c in enumerate(cls.cnames) : if c in kwargs : if float(l[i]) != float(kwargs[c]) : l[i] = float(kwargs[c]) setcomp = True if setcomp : try : self.assign(l) except : msg = ", ".join(map(lambda x,y:x+"=<"+util.clsname(y)+">", cls.cnames, l)) raise TypeError, "in %s(%s), at least one of the components is of an invalid type, check help(%s) " % (cls.__name__, msg, cls.__name__) # units handling self.unit = kwargs.get('unit', None) if self.unit is not None : self.assign([Distance(x, self.unit) for x in self]) def __repr__(self): if hasattr( self, 'unit' ) and self.unit: return "dt.%s(%s, unit='%s')" % (self.__class__.__name__, str(self), self.unit) else: return "dt.%s(%s)" % (self.__class__.__name__, str(self)) # for compatibility with base classes Array that actually hold a nested list in their _data attribute # here, there is no _data attribute as we subclass _api.MVector directly, thus v.data is v # for wraps def _getdata(self): return self.apicls(self) def _setdata(self, value): self.assign(value) def _deldata(self): if hasattr(self.apicls, 'clear') : self.apicls.clear(self) else : raise TypeError, "cannot clear stored elements of %s" % (self.__class__.__name__) data = property(_getdata, _setdata, _deldata, "The Vector/FloatVector/Point/FloatPoint/Color data") # overloads for assign and get though standard way should be to use the data property # to access stored values def assign(self, value): """ Wrap the Vector api assign method """ # don't accept instances as assign works on exact types if type(value) != self.apicls and type(value) != type(self) : if not hasattr(value, '__iter__') : value = (value,) value = self.apicls(*value) self.apicls.assign(self, value) return self # API get, actually not faster than pulling self[i] for such a short structure def get(self): """ Wrap the Vector api get method """ # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... ms = _api.MScriptUtil() l = (0,)*self.size ms.createFromDouble ( *l ) p = ms.asDoublePtr () self.apicls.get(self, p) return tuple([ms.getDoubleArrayItem ( p, i ) for i in xrange(self.size)]) def __len__(self): """ Number of components in the Vector instance, 3 for Vector, 4 for Point and Color """ return self.apicls.__len__(self) # __getitem__ / __setitem__ override # faster to override __getitem__ cause we know Vector only has one dimension def __getitem__(self, i): """ Get component i value from self """ if hasattr(i, '__iter__') : i = list(i) if len(i) == 1 : i = i[0] else : raise IndexError, "class %s instance %s has only %s dimension(s), index %s is out of bounds" % (util.clsname(self), self, self.ndim, i) if isinstance(i, slice) : return _toCompOrArrayInstance(list(self)[i], VectorN) try : return _toCompOrArrayInstance(list(self)[i], VectorN) except : raise IndexError, "class %s instance %s is of size %s, index %s is out of bounds" % (util.clsname(self), self, self.size, i) else : if i < 0 : i = self.size + i if i<self.size and not i<0 : if hasattr(self.apicls, '__getitem__') : return self.apicls.__getitem__(self, i) else : return list(self)[i] else : raise IndexError, "class %s instance %s is of size %s, index %s is out of bounds" % (util.clsname(self), self, self.size, i) # as _api.Vector has no __setitem__ method, so need to reassign the whole Vector def __setitem__(self, i, a): """ Set component i value on self """ v = VectorN(self) v.__setitem__(i, a) self.assign(v) # iterator override # TODO : support for optional __iter__ arguments def __iter__(self, *args, **kwargs): """ Iterate on the api components """ return self.apicls.__iter__(self.data) def __contains__(self, value): """ True if at least one of the vector components is equal to the argument """ return value in self.__iter__() # common operators without an api equivalent are herited from VectorN # operators using the Maya API when applicable, but that can delegate to VectorN def __eq__(self, other): """ u.__eq__(v) <==> u == v Equivalence test """ try : return bool(self.apicls.__eq__(self, other)) except Exception: return bool(super(Vector, self).__eq__(other)) def __ne__(self, other): """ u.__ne__(v) <==> u != v Equivalence test """ return (not self.__eq__(other)) def __neg__(self): """ u.__neg__() <==> -u The unary minus operator. Negates the value of each of the components of u """ return self.__class__(self.apicls.__neg__(self)) def __add__(self, other) : """ u.__add__(v) <==> u+v Returns the result of the addition of u and v if v is convertible to a VectorN (element-wise addition), adds v to every component of u if v is a scalar """ try : return self.__class__._convert(self.apicls.__add__(self, other)) except Exception: return self.__class__._convert(super(Vector, self).__add__(other)) def __radd__(self, other) : """ u.__radd__(v) <==> v+u Returns the result of the addition of u and v if v is convertible to a VectorN (element-wise addition), adds v to every component of u if v is a scalar """ try : return self.__class__._convert(self.apicls.__radd__(self, other)) except Exception: return self.__class__._convert(super(Vector, self).__radd__(other)) def __iadd__(self, other): """ u.__iadd__(v) <==> u += v In place addition of u and v, see __add__ """ try : return self.__class__(self.__add__(other)) except Exception: return NotImplemented def __sub__(self, other) : """ u.__sub__(v) <==> u-v Returns the result of the substraction of v from u if v is convertible to a VectorN (element-wise substration), substract v to every component of u if v is a scalar """ try : return self.__class__._convert(self.apicls.__sub__(self, other)) except Exception: return self.__class__._convert(super(Vector, self).__sub__(other)) def __rsub__(self, other) : """ u.__rsub__(v) <==> v-u Returns the result of the substraction of u from v if v is convertible to a VectorN (element-wise substration), replace every component c of u by v-c if v is a scalar """ try : return self.__class__._convert(self.apicls.__rsub__(self, other)) except Exception: return self.__class__._convert(super(Vector, self).__rsub__(other)) def __isub__(self, other): """ u.__isub__(v) <==> u -= v In place substraction of u and v, see __sub__ """ try : return self.__class__(self.__sub__(other)) except Exception: return NotImplemented def __div__(self, other): """ u.__div__(v) <==> u/v Returns the result of the division of u by v if v is convertible to a VectorN (element-wise division), divide every component of u by v if v is a scalar """ try : return self.__class__._convert(self.apicls.__div__(self, other)) except Exception: return self.__class__._convert(super(Vector, self).__div__(other)) def __rdiv__(self, other): """ u.__rdiv__(v) <==> v/u Returns the result of of the division of v by u if v is convertible to a VectorN (element-wise division), invert every component of u and multiply it by v if v is a scalar """ try : return self.__class__._convert(self.apicls.__rdiv__(self, other)) except Exception: return self.__class__._convert(super(Vector, self).__rdiv__(other)) def __idiv__(self, other): """ u.__idiv__(v) <==> u /= v In place division of u by v, see __div__ """ try : return self.__class__(self.__div__(other)) except Exception: return NotImplemented # action depends on second object type def __mul__(self, other) : """ u.__mul__(v) <==> u*v The multiply '*' operator is mapped to the dot product when both objects are Vectors, to the transformation of u by matrix v when v is a MatrixN, to element wise multiplication when v is a sequence, and multiplies each component of u by v when v is a numeric type. """ try : res = self.apicls.__mul__(self, other) assert res is not NotImplemented except Exception: res = super(Vector, self).__mul__(other) if util.isNumeric(res) or res is NotImplemented: return res else : return self.__class__._convert(res) def __rmul__(self, other): """ u.__rmul__(v) <==> v*u The multiply '*' operator is mapped to the dot product when both objects are Vectors, to the left side multiplication (pre-multiplication) of u by matrix v when v is a MatrixN, to element wise multiplication when v is a sequence, and multiplies each component of u by v when v is a numeric type. """ try : res = self.apicls.__rmul__(self, other) except : res = super(Vector, self).__rmul__(other) if util.isNumeric(res) : return res else : return self.__class__._convert(res) def __imul__(self, other): """ u.__imul__(v) <==> u *= v Valid for Vector * Matrix multiplication, in place transformation of u by Matrix v or Vector by scalar multiplication only """ try : return self.__class__(self.__mul__(other)) except : return NotImplemented # special operators def __xor__(self, other): """ u.__xor__(v) <==> u^v Defines the cross product operator between two 3D vectors, if v is a MatrixN, u^v is equivalent to u.transformAsNormal(v) """ if isinstance(other, VectorN) : return self.cross(other) elif isinstance(other, MatrixN) : return self.transformAsNormal(other) else : return NotImplemented def __ixor__(self, other): """ u.__xor__(v) <==> u^=v Inplace cross product or transformation by inverse transpose of v is v is a MatrixN """ try : return self.__class__(self.__xor__(other)) except : return NotImplemented # wrap of other API MVector methods, we use the api method if possible and delegate to Vector else def isEquivalent(self, other, tol=None): """ Returns true if both arguments considered as Vector are equal within the specified tolerance """ if tol is None : tol = _api.MVector_kTol try : nself, nother = coerce(self, other) except : return False if isinstance(nself, Vector) : return bool(nself.apicls.isEquivalent(nself, nother, tol)) else : return bool(super(Vector, nself).isEquivalent(nother, tol)) def isParallel(self, other, tol=None): """ Returns true if both arguments considered as Vector are parallel within the specified tolerance """ if tol is None : tol = _api.MVector_kTol try : return bool(self.apicls.isParallel(Vector(self), Vector(other), tol)) except : return super(Vector, self).isParallel(other, tol) def distanceTo(self, other): try : return self.apicls.distanceTo(Point(self), Point(other)) except : return super(Vector, self).dist(other) def length(self): """ Return the length of the vector """ return Vector.apicls.length(Vector(self)) def sqlength(self): """ Return the square length of the vector """ return self.dot(self) def normal(self): """ Return a normalized copy of self """ return self.__class__(Vector.apicls.normal(Vector(self))) def normalize(self): """ Performs an in place normalization of self """ if type(self) is Vector : Vector.apicls.normalize(self) else : self.assign(self.normal()) # additional api methods that work on Vector only, and don't have an equivalent on VectorN def rotateTo(self, other): """ u.rotateTo(v) --> Quaternion Returns the Quaternion that represents the rotation of the Vector u into the Vector v around their mutually perpendicular axis. It amounts to rotate u by angle(u, v) around axis(u, v) """ if isinstance(other, Vector) : return Quaternion(Vector.apicls.rotateTo(Vector(self), Vector(other))) else : raise TypeError, "%r is not a Vector instance" % other def rotateBy(self, *args): """ u.rotateBy(*args) --> Vector Returns the result of rotating u by the specified arguments. There are several ways the rotation can be specified: args is a tuple of one Matrix, TransformationMatrix, Quaternion, EulerRotation arg is tuple of 4 arguments, 3 rotation value and an optionnal rotation order args is a tuple of one Vector, the axis and one float, the angle to rotate around that axis in radians""" if args : if len(args) == 2 and isinstance(args[0], Vector) : return self.__class__(self.apicls.rotateBy(self, Quaternion(Vector(args[0]), float(args[1])))) elif len(args) == 1 and isinstance(args[0], Matrix) : return self.__class__(self.apicls.rotateBy(self, args[0].rotate)) else : return self.__class__(self.apicls.rotateBy(self, EulerRotation(unit='radians', *args))) else : return self # def asUnit(self, unit) : # #kUnit = Distance.kUnit(unit) # return self.__class__( [ Distance(x).asUnit(unit) for x in self ] ) # # def asUnit(self) : # return self.asUnit(self.unit) # # def asUIUnit()nits()self) : # return self.asUnit(Distance.getUIUnit()) # # def asInternalUnit(self) : # return self.asUnit(Distance.getInternalUnit()) # # def asMillimeter(self) : # return self.asUnit('millimeter') # def asCentimeters(self) : # return self.asUnit('centimeters') # def asKilometers(self) : # return self.asUnit('kilometers') # def asMeters(self) : # return self.asUnit('meters') # # def asInches(self) : # return self.asUnit('inches') # def asFeet(self) : # return self.asUnit('feet') # def asYards(self) : # return self.asUnit('yards') # def asMiles(self) : # return self.asUnit('miles') # additional api methods that work on Vector only, but can also be delegated to VectorN def transformAsNormal(self, other): """ Returns the vector transformed by the matrix as a normal Normal vectors are not transformed in the same way as position vectors or points. If this vector is treated as a normal vector then it needs to be transformed by post multiplying it by the inverse transpose of the transformation matrix. This method will apply the proper transformation to the vector as if it were a normal. """ if isinstance(other, Matrix) : return self.__class__._convert(Vector.apicls.transformAsNormal(Vector(self), Matrix(other))) else : return self.__class__._convert(super(Vector, self).transformAsNormal(other)) def dot(self, other): """ dot product of two vectors """ if isinstance(other, Vector) : return Vector.apicls.__mul__(Vector(self), Vector(other)) else : return super(Vector, self).dot(other) def cross(self, other): """ cross product, only defined for two 3D vectors """ if isinstance(other, Vector) : return self.__class__._convert(Vector.apicls.__xor__(Vector(self), Vector(other))) else : return self.__class__._convert(super(Vector, self).cross(other)) def axis(self, other, normalize=False): """ u.axis(v) <==> angle(u, v) --> Vector Returns the axis of rotation from u to v as the vector n = u ^ v if the normalize keyword argument is set to True, n is also normalized """ if isinstance(other, Vector) : if normalize : return self.__class__._convert(Vector.apicls.__xor__(Vector(self), Vector(other)).normal()) else : return self.__class__._convert(Vector.apicls.__xor__(Vector(self), Vector(other))) else : return self.__class__._convert(super(Vector, self).axis(other, normalize)) def angle(self, other): """ u.angle(v) <==> angle(u, v) --> float Returns the angle (in radians) between the two vectors u and v Note that this angle is not signed, use axis to know the direction of the rotation """ if isinstance(other, Vector) : return Vector.apicls.angle(Vector(self), Vector(other)) else : return super(Vector, self).angle(other) # methods without an api equivalent # cotan on MVectors only takes 2 arguments def cotan(self, other): """ u.cotan(v) <==> cotan(u, v) --> float : cotangent of the a, b angle, a and b should be MVectors""" return VectorN.cotan(self, other) # rest derived from VectorN class class FloatVector(Vector) : """ A 3 dimensional vector class that wraps Maya's api FloatVector class, It behaves identically to Vector, but it also derives from api's FloatVector to keep api methods happy """ apicls = _api.MFloatVector # Point specific functions def planar(p, *args, **kwargs): """ planar(p[, q, r, s (...), tol=tolerance]) --> bool Returns True if all provided MPoints are planar within given tolerance """ if not isinstance(p, Point) : try : p = Point(p) except : raise TypeError, "%s is not convertible to type Point, planar is only defined for n MPoints" % (util.clsname(p)) return p.planar(*args, **kwargs) def center(p, *args): """ center(p[, q, r, s (...)]) --> Point Returns the Point that is the center of p, q, r, s (...) """ if not isinstance(p, Point) : try : p = Point(p) except : raise TypeError, "%s is not convertible to type Point, center is only defined for n MPoints" % (util.clsname(p)) return p.center(*args) def bWeights(p, *args): """ bWeights(p[, p0, p1, (...), pn]) --> tuple Returns a tuple of (n0, n1, ...) normalized barycentric weights so that n0*p0 + n1*p1 + ... = p """ if not isinstance(p, Point) : try : p = Point(p) except : raise TypeError, "%s is not convertible to type Point, bWeights is only defined for n MPoints" % (util.clsname(p)) return p.bWeights(*args) class Point(Vector): """ A 4 dimensional vector class that wraps Maya's api Point class, """ apicls = _api.MPoint cnames = ('x', 'y', 'z', 'w') shape = (4,) def __melobject__(self): """Special method for returning a mel-friendly representation. In this case, a cartesian 3D point """ return self.cartesian() # # base methods are inherited from Vector # we only show the x, y, z components on an iter def __len__(self): l = len(self.data) if self.w == 1.0 : l -= 1 return l def __iter__(self, *args, **kwargs): """ Iterate on the api components """ l = len(self) for c in list(self.apicls.__iter__(self.data))[:l] : yield c # modified operators, when adding 2 Point consider second as Vector def __add__(self, other) : """ u.__add__(v) <==> u+v Returns the result of the addition of u and v if v is convertible to a VectorN (element-wise addition), adds v to every component of u if v is a scalar """ # prb with coerce when delegating to VectorN, either redefine coerce for Point or other fix # if isinstance(other, Point) : # other = Vector(other) try : other = Vector(other) except : pass try : return self.__class__._convert(self.apicls.__add__(self, other)) except : return self.__class__._convert(super(Vector, self).__add__(other)) def __radd__(self, other) : """ u.__radd__(v) <==> v+u Returns the result of the addition of u and v if v is convertible to a VectorN (element-wise addition), adds v to every component of u if v is a scalar """ if isinstance(other, Point) : other = Vector(other) try : return self.__class__._convert(self.apicls.__radd__(self, other)) except : return self.__class__._convert(super(Point, self).__radd__(other)) def __iadd__(self, other): """ u.__iadd__(v) <==> u += v In place addition of u and v, see __add__ """ try : return self.__class__(self.__add__(other)) except : return NotImplemented # specific api methods def cartesianize (self) : """ p.cartesianize() --> Point If the point instance p is of the form P(W*x, W*y, W*z, W), for some scale factor W != 0, then it is reset to be P(x, y, z, 1). This will only work correctly if the point is in homogenous form or cartesian form. If the point is in rational form, the results are not defined. """ return self.__class__(self.apicls.cartesianize(self)) def cartesian (self) : """ p.cartesian() --> Point Returns the cartesianized version of p, without changing p. """ t = copy.deepcopy(self) self.apicls.cartesianize(t) return t def rationalize (self) : """ p.rationalize() --> Point If the point instance p is of the form P(W*x, W*y, W*z, W) (ie. is in homogenous or (for W==1) cartesian form), for some scale factor W != 0, then it is reset to be P(x, y, z, W). This will only work correctly if the point is in homogenous or cartesian form. If the point is already in rational form, the results are not defined. """ return self.__class__(self.apicls.rationalize(self)) def rational (self) : """ p.rational() --> Point Returns the rationalized version of p, without changing p. """ t = copy.deepcopy(self) self.apicls.rationalize(t) return t def homogenize (self) : """ p.homogenize() --> Point If the point instance p is of the form P(x, y, z, W) (ie. is in rational or (for W==1) cartesian form), for some scale factor W != 0, then it is reset to be P(W*x, W*y, W*z, W). """ return self.__class__(self.apicls.homogenize(self)) def homogen (self) : """ p.homogen() --> Point Returns the homogenized version of p, without changing p. """ t = copy.deepcopy(self) self.apicls.homogenize(t) return t # additionnal methods def isEquivalent(self, other, tol=None): """ Returns true if both arguments considered as Point are equal within the specified tolerance """ if tol is None : tol = _api.MPoint_kTol try : nself, nother = coerce(self, other) except : return False if isinstance(nself, Point) : return bool(nself.apicls.isEquivalent(nself, nother, tol)) else : return bool(super(Point, nself).isEquivalent(nother, tol)) def axis(self, start, end, normalize=False): """ a.axis(b, c) --> Vector Returns the axis of rotation from point b to c around a as the vector n = (b-a)^(c-a) if the normalize keyword argument is set to True, n is also normalized """ return Vector.axis(start-self, end-self, normalize=normalize) def angle(self, start, end): """ a.angle(b, c) --> float Returns the angle (in radians) of rotation from point b to c around a. Note that this angle is not signed, use axis to know the direction of the rotation """ return Vector.angle(start-self, end-self) def cotan(self, start, end): """ a.cotan(b, c) --> float : cotangent of the (b-a), (c-a) angle, a, b, and c should be MPoints representing points a, b, c""" return VectorN.cotan(start-self, end-self) def planar(self, *args, **kwargs): """ p.planar(q, r, s (...), tol=tolerance) --> bool Returns True if all provided points are planar within given tolerance """ if len(args) > 2 : tol = kwargs.get('tol', None) n = (args[0]-self)^(args[1]-self) return reduce(operator.and_, map(lambda x:n.isParallel(x, tol), [(args[0]-self)^(a-self) for a in args[2:]]), True) else : return True def center(self, *args): """ p.center(q, r, s (...)) --> Point Returns the Point that is the center of p, q, r, s (...) """ return sum((self,)+args) / float(len(args) + 1) def bWeights(self, *args): """ p.bWeights(p0, p1, (...), pn) --> tuple Returns a tuple of (n0, n1, ...) normalized barycentric weights so that n0*p0 + n1*p1 + ... = p. This method works for n points defining a concave or convex n sided face, always returns positive normalized weights, and is continuous on the face limits (on the edges), but the n points must be coplanar, and p must be inside the face delimited by (p0, ..., pn) """ if args : p = self q = list(args) np = len(q) w = VectorN(0.0, size=np) weightSum = 0.0 pOnEdge = False; tol = _api.MPoint_kTol # all args should be MPoints for i in xrange(np) : if not isinstance(q[i], Point) : try : q[i] = Point(q[i]) except : raise TypeError, "cannot convert %s to Point, bWeights is defined for n MPoints" % (util.clsname(q[i])) # if p sits on an edge, it' a limit case and there is an easy solution, # all weights are 0 but for the 2 edge end points for i in xrange(np) : next = (i+1) % np e = ((q[next]-q[i]) ^ (p-q[i])).sqlength() l = (q[next]-q[i]).sqlength() if e <= (tol * l) : if l < tol : # p is on a 0 length edge, point and next point are on top of each other, as is p then w[i] = 0.5 w[next] = 0.5 else : # p is somewhere on that edge between point and next point di = (p-q[i]).length() w[next] = float(di / sqrt(l)) w[i] = 1.0 - w[next] # in both case update the weights sum and mark p as being on an edge, # problem is solved weightSum += 1.0 pOnEdge = True break # If p not on edge, use the cotangents method if not pOnEdge : for i in xrange(np) : prev = (i+np-1) % np next = (i+1) % np lenSq = (p - q[i]).sqlength() w[i] = ( q[i].cotan(p, q[prev]) + q[i].cotan(p, q[next]) ) / lenSq weightSum += w[i] # then normalize result if abs(weightSum) : w /= weightSum else : raise ValueError, "failed to compute bWeights for %s and %s.\nThe point bWeights are computed for must be inside the planar face delimited by the n argument points" % (self, args) return tuple(w) else : return () class FloatPoint(Point) : """ A 4 dimensional vector class that wraps Maya's api FloatPoint class, It behaves identically to Point, but it also derives from api's FloatPoint to keep api methods happy """ apicls = _api.MFloatPoint class Color(Vector): """ A 4 dimensional vector class that wraps Maya's api Color class, It stores the r, g, b, a components of the color, as normalized (Python) floats """ apicls = _api.MColor cnames = ('r', 'g', 'b', 'a') shape = (4,) # modes = ('rgb', 'hsv', 'cmy', 'cmyk') modes = ('rgb', 'hsv') # constants red = _api.MColor(1.0, 0.0, 0.0) green = _api.MColor(0.0, 1.0, 0.0) blue = _api.MColor(0.0, 0.0, 1.0) white = _api.MColor(1.0, 1.0, 1.0) black = _api.MColor(0.0, 0.0, 0.0) opaque = _api.MColor(0.0, 0.0, 0.0, 1.0) clear = _api.MColor(0.0, 0.0, 0.0, 0.0) # static methods @staticmethod def rgbtohsv(c): c = tuple(c) return tuple(colorsys.rgb_to_hsv(*clamp(c[:3]))+c[3:4]) @staticmethod def hsvtorgb(c): c = tuple(c) # return colorsys.hsv_to_rgb(clamp(c[0]), clamp(c[1]), clamp(c[2])) return tuple(colorsys.hsv_to_rgb(*clamp(c[:3]))+c[3:4]) # TODO : could define rgb and hsv iterators and allow __setitem__ and __getitem__ on these iterators # like (it's more simple) it's done in ArrayIter def _getrgba(self): return tuple(self) def _setrgba(self, value): if not hasattr(value, '__iter__') : # the way api interprets a single value # value = (None, None, None, value) value = (value,)*4 l = list(self) for i, v in enumerate(value[:4]) : if v is not None : l[i] = float(v) self.assign(*l) rgba = property(_getrgba, _setrgba, None, "The r,g,b,a Color components""") def _getrgb(self): return self.rgba[:3] def _setrgb(self, value): if not hasattr(value, '__iter__') : value = (value,)*3 self.rgba = value[:3] rgb = property(_getrgb, _setrgb, None, "The r,g,b Color components""") def _gethsva(self): return tuple(Color.rgbtohsv(self)) def _sethsva(self, value): if not hasattr(value, '__iter__') : # the way api interprets a single value # value = (None, None, None, value) value = (value,)*4 l = list(Color.rgbtohsv(self)) for i, v in enumerate(value[:4]) : if v is not None : l[i] = float(v) self.assign(*Color.hsvtorgb(self)) hsva = property(_gethsva, _sethsva, None, "The h,s,v,a Color components""") def _gethsv(self): return tuple(Color.rgbtohsv(self))[:3] def _sethsv(self, value): if not hasattr(value, '__iter__') : value = (value,)*3 self.hsva = value[:3] hsv = property(_gethsv, _sethsv, None, "The h,s,v,a Color components""") def _geth(self): return self.hsva[0] def _seth(self, value): self.hsva = (value, None, None, None) h = property(_geth, _seth, None, "The h Color component""") def _gets(self): return self.hsva[1] def _sets(self, value): self.hsva = (None, value, None, None) s = property(_gets, _sets, None, "The s Color component""") def _getv(self): return self.hsva[2] def _setv(self, value): self.hsva = (None, None, value, None) v = property(_getv, _setv, None, "The v Color component""") # __new__ is herited from Point/Vector, need to override __init__ to accept hsv mode though def __init__(self, *args, **kwargs): """ Init a Color instance Can pass one argument being another Color instance , or the color components """ cls = self.__class__ mode = kwargs.get('mode', None) if mode is not None and mode not in cls.modes : raise ValueError, "unknown mode %s for %s" % (mode, util.clsname(self)) # can also use the form <componentname>=<number> # for now supports only rgb and hsv flags hsvflag = {} rgbflag = {} for a in 'hsv' : if a in kwargs : hsvflag[a] = kwargs[a] for a in 'rgb' : if a in kwargs : rgbflag[a] = kwargs[a] # can't mix them if hsvflag and rgbflag : raise ValueError, "can not mix r,g,b and h,s,v keyword arguments in a %s declaration" % util.clsname(self) # if no mode specified, guess from what keyword arguments where used, else use 'rgb' as default if mode is None : if hsvflag : mode = 'hsv' else : mode = 'rgb' # can't specify a mode and use keywords of other modes if mode is not 'hsv' and hsvflag : raise ValueError, "Can not use h,s,v keyword arguments while specifying %s mode in %s" % (mode, util.clsname(self)) elif mode is not 'rgb' and rgbflag : raise ValueError, "Can not use r,g,b keyword arguments while specifying %s mode in %s" % (mode, util.clsname(self)) # NOTE: do not try to use mode with _api.Color, it seems bugged as of 2008 #import colorsys #colorsys.rgb_to_hsv(0.0, 0.0, 1.0) ## Result: (0.66666666666666663, 1.0, 1.0) # #c = _api.Color(_api.Color.kHSV, 0.66666666666666663, 1.0, 1.0) #print "# Result: ",c[0], c[1], c[2], c[3]," #" ## Result: 1.0 0.666666686535 1.0 1.0 # #c = _api.Color(_api.Color.kHSV, 0.66666666666666663*360, 1.0, 1.0) #print "# Result: ",c[0], c[1], c[2], c[3]," #" ## Result: 1.0 240.0 1.0 1.0 # #colorsys.hsv_to_rgb(0.66666666666666663, 1.0, 1.0) ## Result: (0.0, 0.0, 1.0) # # we'll use Color only to store RGB values internally and do the conversion a read/write if desired # which I think make more sense anyway # quantize (255, 65535, no quantize means colors are 0.0-1.0 float values) # Initializing api's Color with int values seems also not to always behave so we quantize first and # use a float init always quantize = kwargs.get('quantize', None) if quantize is not None : try : quantize = float(quantize) except : raise ValueError, "quantize must be a numeric value, not %s" % (util.clsname(quantize)) # can be initilized with a single argument (other Color, Vector, VectorN) if len(args)==1 : args = args[0] # we dont rely much on Color api as it doesn't seem totally finished, and do some things directly here if isinstance(args, self.__class__) or isinstance(args, self.apicls) : # alternatively could be just ignored / output as warning if quantize : raise ValueError, "Can not quantize a Color argument, a Color is always stored internally as float color" % (mode, util.clsname(self)) if mode == 'rgb' : args = VectorN(args) elif mode == 'hsv' : args = VectorN(cls.rgbtohsv(args)) else : # single alpha value, as understood by api will break coerce behavior in operations # where other operand is a scalar #if not hasattr(args, '__iter__') : # args = VectorN(0.0, 0.0, 0.0, args) if hasattr(args, '__len__') : shape = (min(len(args), cls.size),) else : shape = cls.shape args = VectorN(args, shape=shape) # quantize if needed if quantize : args /= quantize # pad to a full Color size args.stack(self[len(args):]) # apply keywords arguments, and convert if mode is not rgb if mode == 'rgb' : if rgbflag : for i, a in enumerate('rgb') : if a in rgbflag : if quantize : args[i] = float(rgbflag[a]) / quantize else : args[i] = float(rgbflag[a]) elif mode == 'hsv' : if hsvflag : for i, a in enumerate('hsv') : if a in hsvflag : if quantize : args[i] = float(hsvflag[a]) / quantize else : args[i] = float(hsvflag[a]) args = VectorN(cls.hsvtorgb(args)) # finally alpha keyword a = kwargs.get('a', None) if a is not None : if quantize : args[-1] = float(a) / quantize else : args[-1] = float(a) try : self.assign(args) except : msg = ", ".join(map(lambda x,y:x+"=<"+util.clsname(y)+">", mode, args)) raise TypeError, "in %s(%s), at least one of the components is of an invalid type, check help(%s) " % (util.clsname(self), msg, util.clsname(self)) def __melobject__(self): """Special method for returning a mel-friendly representation. In this case, a 3-component color (RGB) """ return [self.r, self.g, self.b] # overriden operators # defined for two MColors only def __add__(self, other) : """ c.__add__(d) <==> c+d Returns the result of the addition of MColors c and d if d is convertible to a Color, adds d to every component of c if d is a scalar """ # prb with coerce when delegating to VectorN, either redefine coerce for Point or other fix # if isinstance(other, Point) : # other = Vector(other) try : other = Color(other) except : pass try : return self.__class__._convert(self.apicls.__add__(self, other)) except : return self.__class__._convert(super(Vector, self).__add__(other)) def __radd__(self, other) : """ c.__radd__(d) <==> d+c Returns the result of the addition of MColors c and d if d is convertible to a Color, adds d to every component of c if d is a scalar """ try : other = Color(other) except : pass try : return self.__class__._convert(self.apicls.__radd__(self, other)) except : return self.__class__._convert(super(Point, self).__radd__(other)) def __iadd__(self, other): """ c.__iadd__(d) <==> c += d In place addition of c and d, see __add__ """ try : return self.__class__(self.__add__(other)) except : return NotImplemented def __sub__(self, other) : """ c.__add__(d) <==> c+d Returns the result of the substraction of Color d from c if d is convertible to a Color, substract d from every component of c if d is a scalar """ try : other = Color(other) except : pass try : return self.__class__._convert(self.apicls.__sub__(self, other)) except : return self.__class__._convert(super(Vector, self).__sub__(other)) def __rsub__(self, other) : """ c.__rsub__(d) <==> d-c Returns the result of the substraction of Color c from d if d is convertible to a Color, replace every component c[i] of c by d-c[i] if d is a scalar """ try : other = Color(other) except : pass try : return self.__class__._convert(self.apicls.__rsub__(self, other)) except : return self.__class__._convert(super(Point, self).__rsub__(other)) def __isub__(self, other): """ c.__isub__(d) <==> c -= d In place substraction of d from c, see __sub__ """ try : return self.__class__(self.__sub__(other)) except : return NotImplemented # action depends on second object type # TODO : would be nice to define LUT classes and allow MColor * LUT transform # overloaded operators def __mul__(self, other): """ a.__mul__(b) <==> a*b If b is a 1D sequence (Array, VectorN, Color), __mul__ is mapped to element-wise multiplication, If b is a MatrixN, __mul__ is similar to Point a by MatrixN b multiplication (post multiplication or transformation of a by b), multiplies every component of a by b if b is a single numeric value """ if isinstance(other, MatrixN) : # will defer to MatrixN rmul return NotImplemented else : # will defer to Array.__mul__ return Array.__mul__(self, other) def __rmul__(self, other): """ a.__rmul__(b) <==> b*a If b is a 1D sequence (Array, VectorN, Color), __mul__ is mapped to element-wise multiplication, If b is a MatrixN, __mul__ is similar to MatrixN b by Point a matrix multiplication, multiplies every component of a by b if b is a single numeric value """ if isinstance(other, MatrixN) : # will defer to MatrixN mul return NotImplemented else : # will defer to Array.__rmul__ return Array.__rmul__(self, other) def __imul__(self, other): """ a.__imul__(b) <==> a *= b In place multiplication of VectorN a and b, see __mul__, result must fit a's type """ res = self*other if isinstance(res, self.__class__) : return self.__class__(res) else : raise TypeError, "result of in place multiplication of %s by %s is not a %s" % (clsname(self), clsname(other), clsname(self)) # additionnal methods, to be extended def over(self, other): """ c1.over(c2): Composites c1 over other c2 using c1's alpha, the resulting color has the alpha of c2 """ if isinstance(other, Color) : a = self.a return Color(Vector(other).blend(Vector(self), self.a), a=other.a) else : raise TypeError, "over is defined for Color instances, not %s" % (util.clsname(other)) # return Vector instead ? Keeping alpha doesn't make much sense def premult(self): """ Premultiply Color r, g and b by it's alpha and resets alpha to 1.0 """ return self.__class__(Vector(self)*self.a) def gamma(self, g): """ c.gamma(g) applies gamma correction g to Color c, g can be a scalar and then will be applied to r, g, b or an iterable of up to 3 (r, g, b) independant gamma correction values """ if not hasattr(g, '__iter__') : g = (g,)*3+(1.0,) else : g = g[:3]+(1.0,)*(4-len(g[:3])) return gamma(self, g) def hsvblend(self, other, weight=0.5): """ c1.hsvblend(c2) --> Color Returns the result of blending c1 with c2 in hsv space, using the given weight """ c1 = list(self.hsva) c2 = list(other.hsva) if abs(c2[0]-c1[0]) >= 0.5 : if abs(c2[0]-c1[0]) == 0.5 : c1[1], c2[1] = 0.0, 0.0 if c1[0] > 0.5 : c1[0] -= 1.0 if c2[0] > 0.5 : c2[0] -= 1.0 c = blend(c1, c2, weight=weight) if c[0] < 0.0 : c[0] += 1.0 return self.__class__(c, mode='hsv') # to specify space of transforms class Space(_api.MSpace): apicls = _api.MSpace __metaclass__ = _factories.MetaMayaTypeWrapper pass Spaces = Space.Space def _fixSpace(): # fix the Space enumerator keys = Space.Space._keys.copy() #print keys val = keys.pop('postTransform', None) if val: keys['object'] = val Space.Space = util.Enum( 'Space', keys ) Spaces = Space.Space #_fixSpace() #kInvalid # kTransform #Transform matrix (relative) space # kPreTransform #Pre-transform matrix (geometry) # kPostTransform #Post-transform matrix (world) space # kWorld #transform in world space # kObject #Same as pre-transform space # kLast # sadly TransformationMatrix.RotationOrder and EulerRotation.RotationOrder don't match #class MRotationOrder(int): # pass #kInvalid # kXYZ # kYZX # kZXY # kXZY # kYXZ # kZYX # kLast # kXYZ # kYZX # kZXY # kXZY # kYXZ # kZYX # functions that work on MatrixN (det(), inv(), ...) herited from arrays # and properly defer to the class methods # For row, column order, see the definition of a TransformationMatrix in docs : # T = | 1 0 0 0 | # | 0 1 0 0 | # | 0 0 1 0 | # | tx ty tz 1 | # and m(r, c) should return value of cell at r row and c column : # t = _api.TransformationMatrix() # t.setTranslation(_api.Vector(1, 2, 3), _api.MSpace.kWorld) # m = t.asMatrix() # mm(3,0) # 1.0 # mm(3,1) # 2.0 # mm(3,2) # 3.0 class Matrix(MatrixN): """ A 4x4 transformation matrix based on api Matrix >>> from pymel.all import * >>> import pymel.core.datatypes as dt >>> >>> i = dt.Matrix() >>> print i.formated() [[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]] >>> v = dt.Matrix(1, 2, 3) >>> print v.formated() [[1.0, 2.0, 3.0, 0.0], [1.0, 2.0, 3.0, 0.0], [1.0, 2.0, 3.0, 0.0], [1.0, 2.0, 3.0, 0.0]] """ __metaclass__ = MetaMayaArrayTypeWrapper apicls = _api.MMatrix shape = (4, 4) cnames = ('a00', 'a01', 'a02', 'a03', 'a10', 'a11', 'a12', 'a13', 'a20', 'a21', 'a22', 'a23', 'a30', 'a31', 'a32', 'a33' ) # constants identity = _api.MMatrix() def __new__(cls, *args, **kwargs): shape = kwargs.get('shape', None) ndim = kwargs.get('ndim', None) size = kwargs.get('size', None) # will default to class constant shape = (4, 4), so it's just an error check to catch invalid shapes, # as no other option is actually possible on Matrix, but this method could be used to allow wrapping # of Maya array classes that can have a variable number of elements shape, ndim, size = cls._expandshape(shape, ndim, size) new = cls.apicls.__new__(cls) cls.apicls.__init__(new) return new def __init__(self, *args, **kwargs): """ __init__ method, valid for Vector, Point and Color classes """ cls = self.__class__ if args : # allow both forms for arguments if len(args)==1 and hasattr(args[0], '__iter__') : args = args[0] # shape = kwargs.get('shape', None) # ndim = kwargs.get('ndim', None) # size = kwargs.get('size', None) # if shape is not None or ndim is not None or size is not None : # shape, ndim, size = cls._expandshape(shape, ndim, size) # args = MatrixN(args, shape=shape, ndim=ndim, size=size) # shortcut when a direct api init is possible try : self.assign(args) except : super(MatrixN, self).__init__(*args) # value = list(Matrix(value, shape=self.shape).flat) # data = self.apicls() # _api.MScriptUtil.createMatrixFromList ( value, data ) if hasattr(cls, 'cnames') and len(set(cls.cnames) & set(kwargs)) : # can also use the form <componentname>=<number> l = list(self.flat) setcomp = False for i, c in enumerate(cls.cnames) : if c in kwargs : if float(l[i]) != float(kwargs[c]) : l[i] = float(kwargs[c]) setcomp = True if setcomp : try : self.assign(l) except : msg = ", ".join(map(lambda x,y:x+"=<"+util.clsname(y)+">", cls.cnames, l)) raise TypeError, "in %s(%s), at least one of the components is of an invalid type, check help(%s) " % (cls.__name__, msg, cls.__name__) # for compatibility with base classes Array that actually hold a nested list in their _data attribute # here, there is no _data attribute as we subclass _api.Vector directly, thus v.data is v # for wraps def _getdata(self): return self def _setdata(self, value): self.assign(value) def _deldata(self): if hasattr(self.apicls, 'clear') : self.apicls.clear(self) else : raise TypeError, "cannot clear stored elements of %s" % (self.__class__.__name__) data = property(_getdata, _setdata, _deldata, "The Matrix/FloatMatrix/TransformationMatrix/Quaternion/EulerRotation data") # set properties for easy acces to translation / rotation / scale of a Matrix or derived class # some of these will only yield dependable results if Matrix is a TransformationMatrix and some # will always be zero for some classes (ie only rotation has a value on a Quaternion def _getTranslate(self): t = TransformationMatrix(self) return Vector(t.getTranslation( _api.MSpace.kTransform) ) def _setTranslate(self, value): t = TransformationMatrix(self) t.setTranslation ( Vector(value), _api.MSpace.kTransform ) self.assign(t.asMatrix()) translate = property(_getTranslate, _setTranslate, None, "The translation expressed in this Matrix, in transform space") def _getRotate(self): t = TransformationMatrix(self) return Quaternion(t.rotation()) def _setRotate(self, value): t = TransformationMatrix(self) q = Quaternion(value) t.rotateTo(q) # values = (q.x, q.y, q.z, q.w) # t.setRotationQuaternion(q.x, q.y, q.z, q.w) self.assign(t.asMatrix()) rotate = property(_getRotate, _setRotate, None, "The rotation expressed in this Matrix, in transform space") def _getScale(self): # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... t = TransformationMatrix(self) ms = _api.MScriptUtil() ms.createFromDouble ( 1.0, 1.0, 1.0 ) p = ms.asDoublePtr () t.getScale (p, _api.MSpace.kTransform); return Vector([ms.getDoubleArrayItem (p, i) for i in range(3)]) def _setScale(self, value): t = TransformationMatrix(self) # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... ms = _api.MScriptUtil() ms.createFromDouble (*Vector(value)) p = ms.asDoublePtr () t.setScale ( p, _api.MSpace.kTransform) self.assign(t.asMatrix()) scale = property(_getScale, _setScale, None, "The scale expressed in this Matrix, in transform space") def __melobject__(self): """Special method for returning a mel-friendly representation. In this case, a flat list of 16 values """ return [ x for x in self.flat ] # some Matrix derived classes can actually be represented as matrix but not stored # internally as such by the API def asMatrix(self, percent=None): "The matrix representation for this Matrix/TransformationMatrix/Quaternion/EulerRotation instance" if percent is not None and percent != 1.0 : if type(self) is not TransformationMatrix : self = TransformationMatrix(self) return Matrix(self.apicls.asMatrix(self, percent)) else : if type(self) is Matrix : return self else : return Matrix(self.apicls.asMatrix(self)) matrix = property(asMatrix, None, None, "The Matrix representation for this Matrix/TransformationMatrix/Quaternion/EulerRotation instance") # overloads for assign and get though standard way should be to use the data property # to access stored values def assign(self, value): # don't accept instances as assign works on exact _api.Matrix type data = None if type(value) == self.apicls or type(value) == type(self) : data = value elif hasattr(value, 'asMatrix') : data = value.asMatrix() else : value = list(MatrixN(value).flat) if len(value) == self.size : data = self.apicls() if isinstance(data, _api.MFloatMatrix): _api.MScriptUtil.createFloatMatrixFromList ( value, data ) elif isinstance(data, _api.MMatrix): _api.MScriptUtil.createMatrixFromList ( value, data ) else: tmp = _api.MMatrix() _api.MScriptUtil.createMatrixFromList ( value, tmp ) data = self.apicls( tmp ) else : raise TypeError, "cannot assign %s to a %s" % (value, util.clsname(self)) self.apicls.assign(self, data) return self # API get, actually not faster than pulling self[i] for such a short structure def get(self): """ Wrap the Matrix api get method """ mat = self.matrix return tuple(tuple(_api.MScriptUtil.getDoubleArrayItem ( _api.MMatrix.__getitem__(mat, r), c) for c in xrange(Matrix.shape[1])) for r in xrange(Matrix.shape[0])) # ptr = _api.Matrix(self.matrix).matrix # return tuple(tuple(_api.MScriptUtil.getDouble2ArrayItem ( ptr, r, c) for c in xrange(Matrix.shape[1])) for r in xrange(Matrix.shape[0])) def __len__(self): """ Number of components in the Matrix instance """ return self.apicls.__len__(self) # iterator override # TODO : support for optionnal __iter__ arguments def __iter__(self, *args, **kwargs): """ Iterate on the Matrix rows """ return self.apicls.__iter__(self.data) # contains is herited from Array contains # __getitem__ / __setitem__ override def __getitem__(self, index): """ m.__getitem__(index) <==> m[index] Get component index value from self. index can be a single numeric value or slice, thus one or more rows will be returned, or a row,column tuple of numeric values / slices """ m = MatrixN(self) # print list(m) return m.__getitem__(index) # return super(MatrixN, self).__getitem__(index) # deprecated and __getitem__ should accept slices anyway def __getslice__(self, start, end): return self.__getitem__(slice(start, end)) # as _api.Matrix has no __setitem__ method def __setitem__(self, index, value): """ m.__setitem__(index, value) <==> m[index] = value Set value of component index on self index can be a single numeric value or slice, thus one or more rows will be returned, or a row,column tuple of numeric values / slices """ m = MatrixN(self) m.__setitem__(index, value) self.assign(m) # deprecated and __setitem__ should accept slices anyway def __setslice__(self, start, end, value): self.__setitem__(slice(start, end), value) def __delitem__(self, index) : """ Cannot delete from a class with a fixed shape """ raise TypeError, "deleting %s from an instance of class %s will make it incompatible with class shape" % (index, clsname(self)) def __delslice__(self, start, end): self.__delitem__(slice(start, end)) # TODO : wrap double Matrix:: operator() (unsigned int row, unsigned int col ) const # common operators herited from MatrixN # operators using the Maya API when applicable def __eq__(self, other): """ m.__eq__(v) <==> m == v Equivalence test """ try : return bool(self.apicls.__eq__(self, other)) except : return bool(super(Matrix, self).__eq__(other)) def __ne__(self, other): """ m.__ne__(v) <==> m != v Equivalence test """ return (not self.__eq__(other)) def __neg__(self): """ m.__neg__() <==> -m The unary minus operator. Negates the value of each of the components of m """ return self.__class__(self.apicls.__neg__(self)) def __add__(self, other) : """ m.__add__(v) <==> m+v Returns the result of the addition of m and v if v is convertible to a MatrixN (element-wise addition), adds v to every component of m if v is a scalar """ try : return self.__class__._convert(self.apicls.__add__(self, other)) except : return self.__class__._convert(super(Matrix, self).__add__(other)) def __radd__(self, other) : """ m.__radd__(v) <==> v+m Returns the result of the addition of m and v if v is convertible to a MatrixN (element-wise addition), adds v to every component of m if v is a scalar """ try : return self.__class__._convert(self.apicls.__radd__(self, other)) except : return self.__class__._convert(super(Matrix, self).__radd__(other)) def __iadd__(self, other): """ m.__iadd__(v) <==> m += v In place addition of m and v, see __add__ """ try : return self.__class__(self.__add__(other)) except : return NotImplemented def __sub__(self, other) : """ m.__sub__(v) <==> m-v Returns the result of the substraction of v from m if v is convertible to a MatrixN (element-wise substration), substract v to every component of m if v is a scalar """ try : return self.__class__._convert(self.apicls.__sub__(self, other)) except : return self.__class__._convert(super(Matrix, self).__sub__(other)) def __rsub__(self, other) : """ m.__rsub__(v) <==> v-m Returns the result of the substraction of m from v if v is convertible to a MatrixN (element-wise substration), replace every component c of m by v-c if v is a scalar """ try : return self.__class__._convert(self.apicls.__rsub__(self, other)) except : return self.__class__._convert(super(Matrix, self).__rsub__(other)) def __isub__(self, other): """ m.__isub__(v) <==> m -= v In place substraction of m and v, see __sub__ """ try : return self.__class__(self.__sub__(other)) except : return NotImplemented # action depends on second object type def __mul__(self, other) : """ m.__mul__(x) <==> m*x If x is a MatrixN, __mul__ is mapped to matrix multiplication m*x, if x is a VectorN, to MatrixN by VectorN multiplication. Otherwise, returns the result of the element wise multiplication of m and x if x is convertible to Array, multiplies every component of b by x if x is a single numeric value """ try : return self.__class__._convert(self.apicls.__mul__(self, other)) except : return self.__class__._convert(super(Matrix, self).__mul__(other)) def __rmul__(self, other): """ m.__rmul__(x) <==> x*m If x is a MatrixN, __rmul__ is mapped to matrix multiplication x*m, if x is a VectorN (or Vector or Point or Color), to transformation, ie VectorN by MatrixN multiplication. Otherwise, returns the result of the element wise multiplication of m and x if x is convertible to Array, multiplies every component of m by x if x is a single numeric value """ try : return self.__class__._convert(self.apicls.__rmul__(self, other)) except : return self.__class__._convert(super(Matrix, self).__rmul__(other)) def __imul__(self, other): """ m.__imul__(n) <==> m *= n Valid for Matrix * Matrix multiplication, in place multiplication of MatrixN m by MatrixN n """ try : return self.__class__(self.__mul__(other)) except : return NotImplemented # __xor__ will defer to Vector __xor__ # API added methods def setToIdentity (self) : """ m.setToIdentity() <==> m = a * b Sets MatrixN to the identity matrix """ try : self.apicls.setToIdentity(self) except : self.assign(self.__class__()) return self def setToProduct ( self, left, right ) : """ m.setToProduct(a, b) <==> m = a * b Sets MatrixN to the result of the product of MatrixN a and MatrixN b """ try : self.apicls.setToProduct(self.__class__(left), self.__class__(right)) except : self.assign(self.__class__(self.__class__(left) * self.__class__(right))) return self def transpose(self): """ Returns the transposed Matrix """ try : return self.__class__._convert(self.apicls.transpose(self)) except : return self.__class__._convert(super(Matrix, self).transpose()) def inverse(self): """ Returns the inverse Matrix """ try : return self.__class__._convert(self.apicls.inverse(self)) except : return self.__class__._convert(super(Matrix, self).inverse()) def adjoint(self): """ Returns the adjoint (adjugate) Matrix """ try : return self.__class__._convert(self.apicls.adjoint(self)) except : return self.__class__._convert(super(Matrix, self).adjugate()) def homogenize(self): """ Returns a homogenized version of the Matrix """ try : return self.__class__._convert(self.apicls.homogenize(self)) except : return self.__class__._convert(super(Matrix, self).homogenize()) def det(self): """ Returns the determinant of this Matrix instance """ try : return self.apicls.det4x4(self) except : return super(Matrix, self).det() def det4x4(self): """ Returns the 4x4 determinant of this Matrix instance """ try : return self.apicls.det4x4(self) except : return super(Matrix, self[:4,:4]).det() def det3x3(self): """ Returns the determinant of the upper left 3x3 submatrix of this Matrix instance, it's the same as doing det(m[0:3, 0:3]) """ try : return self.apicls.det3x3(self) except : return super(Matrix, self[:3,:3]).det() def isEquivalent(self, other, tol=_api.MVector_kTol): """ Returns true if both arguments considered as Matrix are equal within the specified tolerance """ try : nself, nother = coerce(self, other) except : return False if isinstance(nself, Matrix) : return bool(nself.apicls.isEquivalent(nself, nother, tol)) else : return bool(super(MatrixN, nself).isEquivalent(nother, tol)) def isSingular(self) : """ Returns True if the given Matrix is singular """ try : return bool(self.apicls.isSingular(self)) except : return super(MatrixN, self).isSingular() # additionnal methods def blend(self, other, weight=0.5): """ Returns a 0.0-1.0 scalar weight blend between self and other Matrix, blend mixes Matrix as transformation matrices """ if isinstance(other, Matrix) : return self.__class__(self.weighted(1.0-weight)*other.weighted(weight)) else : return blend(self, other, weight=weight) def weighted(self, weight): """ Returns a 0.0-1.0 scalar weighted blend between identity and self """ if type(self) is not TransformationMatrix : self = TransformationMatrix(self) return self.__class__._convert(self.asMatrix(weight)) class FloatMatrix(Matrix) : """ A 4x4 matrix class that wraps Maya's api FloatMatrix class, It behaves identically to Matrix, but it also derives from api's FloatMatrix to keep api methods happy """ apicls = _api.MFloatMatrix class TransformationMatrix(Matrix): apicls = _api.MTransformationMatrix def _getTranslate(self): return Vector(self.getTranslation(_api.MSpace.kTransform)) def _setTranslate(self, value): self.setTranslation ( Vector(value), _api.MSpace.kTransform ) translate = property(_getTranslate, _setTranslate, None, "The translation expressed in this TransformationMatrix, in transform space") def _getRotate(self): return Quaternion(self.apicls.rotation(self)) def _setRotate(self, value): self.rotateTo(Quaternion(value)) rotate = property(_getRotate, _setRotate, None, "The quaternion rotation expressed in this TransformationMatrix, in transform space") def rotateTo(self, value): '''Set to the given rotation (and result self) Value may be either a Quaternion, EulerRotation object, or a list of floats; if it is floats, if it has length 4 it is interpreted as a Quaternion; if 3, as a EulerRotation. ''' if not isinstance(value, (Quaternion, EulerRotation, _api.MQuaternion, _api.MEulerRotation)): if len(value) == 3: value = EulerRotation(value) elif len(value) == 4: value = Quaternion(value) else: raise ValueError('arg to rotateTo must be a Quaternion, EulerRotation, or an iterable of 3 or 4 floats') return self.__class__(self.apicls.rotateTo(self, value)) def eulerRotation(self): return EulerRotation(self.apicls.eulerRotation(self)) def _getEuler(self): return self.eulerRotation() def _setEuler(self, value): self.rotateTo(EulerRotation(value)) euler = property(_getEuler, _getEuler, None, "The euler rotation expressed in this TransformationMatrix, in transform space") # The apicls getRotation needs a "RotationOrder &" object, which is # impossible to make in python... # So instead, wrap eulerRotation def getRotation(self): return self.eulerRotation() def setRotation(self, *args): self.rotateTo(EulerRotation(*args)) def _getScale(self): # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... ms = _api.MScriptUtil() ms.createFromDouble ( 1.0, 1.0, 1.0 ) p = ms.asDoublePtr () self.getScale (p, _api.MSpace.kTransform); return Vector([ms.getDoubleArrayItem (p, i) for i in range(3)]) def _setScale(self, value): # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... ms = _api.MScriptUtil() ms.createFromDouble (*Vector(value)) p = ms.asDoublePtr () self.setScale ( p, _api.MSpace.kTransform) scale = property(_getScale, _setScale, None, "The scale expressed in this TransformationMatrix, in transform space") class EulerRotation(Array): """ unit handling: >>> from pymel.all import * >>> import pymel.core.datatypes as dt >>> >>> currentUnit(angle='degree') u'degree' >>> e = dt.EulerRotation([math.pi,0,0], unit='radians') >>> e dt.EulerRotation([3.14159265359, 0.0, 0.0], unit='radians') >>> e2 = dt.EulerRotation([180,0,0], unit='degrees') >>> e2 dt.EulerRotation([180.0, 0.0, 0.0]) >>> e.isEquivalent( e2 ) True >>> e == e2 True units are only displayed when they do not match the current ui unit >>> dt.Angle.getUIUnit() # check current angular unit 'degrees' >>> e dt.EulerRotation([3.14159265359, 0.0, 0.0], unit='radians') >>> dt.Angle.setUIUnit('radians') # change to radians >>> e dt.EulerRotation([3.14159265359, 0.0, 0.0]) """ __metaclass__ = MetaMayaArrayTypeWrapper apicls = _api.MEulerRotation shape = (3,) cnames = ('x', 'y', 'z') RotationOrder = _factories.apiClassInfo['MEulerRotation']['pymelEnums']['RotationOrder'] def _getorder(self): return self.RotationOrder[self.apicls.__dict__['order'].__get__(self, self.apicls)] def _setorder(self, val): self.apicls.__dict__['order'].__set__(self, self.RotationOrder.getIndex(val)) order = property(_getorder, _setorder) def __new__(cls, *args, **kwargs): # shape = kwargs.get('shape', None) # ndim = kwargs.get('ndim', None) # size = kwargs.get('size', None) # new = cls.apicls.__new__(cls) cls.apicls.__init__(new) return new def __init__(self, *args, **kwargs): """ __init__ method for EulerRotation """ self.unit = None self.assign(*args, **kwargs) def setDisplayUnit(self, unit): if unit not in Angle.Unit: raise TypeError, "%s is not a valid angular unit. See Angle.Unit for the list of valid units" self.unit = unit def __repr__(self): argStrs = [str(self)] if self.unit != Angle.getUIUnit(): argStrs.append('unit=%r' % self.unit) if self.order != 'XYZ': argStrs.append('order=%r' % str(self.order)) return "dt.%s(%s)" % (self.__class__.__name__, ', '.join(argStrs)) def __iter__(self): for i in range(self.size): yield self[i] def __getitem__(self, i): return Angle( self._getitem(i), 'radians' ).asUnit(self.unit) def __setitem__(self, key, val): kwargs = {} if key in self.cnames: kwargs[key] = val else: kwargs[self.cnames[key]] = val self.assign(**kwargs) # faster to override __getitem__ cause we know Vector only has one dimension def _getitem(self, i): """ Get component i value from self """ if hasattr(i, '__iter__') : i = list(i) if len(i) == 1 : i = i[0] else : raise IndexError, "class %s instance %s has only %s dimension(s), index %s is out of bounds" % (util.clsname(self), self, self.ndim, i) if isinstance(i, slice) : return _toCompOrArrayInstance(list(self)[i], VectorN) try : return _toCompOrArrayInstance(list(self)[i], VectorN) except : raise IndexError, "class %s instance %s is of size %s, index %s is out of bounds" % (util.clsname(self), self, self.size, i) else : if i < 0 : i = self.size + i if i<self.size and not i<0 : if hasattr(self.apicls, '__getitem__') : return self.apicls.__getitem__(self, i) else : return list(self)[i] else : raise IndexError, "class %s instance %s is of size %s, index %s is out of bounds" % (util.clsname(self), self, self.size, i) def assign(self, *args, **kwargs): """ Wrap the Quaternion api assign method """ # After processing, we want to have args be in a format such that # we may do: # apicls.assign(*args) # This means that either: # args is a list/tuple of if 'unit' in kwargs: self.unit = kwargs['unit'] elif self.unit is None: self.unit = Angle.getUIUnit() if len(args) == 1 and isinstance(args[0], _api.MTransformationMatrix): args = [args[0].asMatrix()] # api MEulerRotation assign accepts Matrix, Quaternion and EulerRotation validSingleObjs = (_api.MMatrix, _api.MQuaternion, _api.MEulerRotation) if len(args) == 1 and isinstance(args[0], validSingleObjs): self.unit = 'radians' self.apicls.assign(self, args[0]) elif args: if len(args) == 1: args = list(args[0]) elif len(args) == 2 and isinstance(args[1], (basestring, util.EnumValue) ): args = list(args[0]) + [args[1]] else: # convert to list, as we may have to do modifications args = list(args) # If only 3 rotation angles supplied, and current order is # not default, make sure we maintain it if self.order != 'XYZ' and len(args) == 3: args.append(self.apicls.__dict__['order'].__get__(self, self.apicls)) elif len(args) == 4 and isinstance(args[3], (basestring, util.EnumValue) ) : # allow to initialize directly from 3 rotations and a rotation order as string args[3] = self.RotationOrder.getIndex(args[3]) # In case they do something like pass in a mix of Angle objects and # float numbers, convert to correct unit one-by-one... for i in xrange(3): if isinstance(args[i], Angle): args[i] = args[i].asUnit('radians') elif self.unit != 'radians' and not isinstance(args[i], Angle): args[i] = Angle(args[i], self.unit).asUnit('radians') self.apicls.setValue(self, *args) # We do kwargs as a separate step after args, instead of trying to combine # them, in case they do something like pass in a EulerRotation(myMatrix, y=2) if hasattr(self, 'cnames') and len(set(self.cnames) & set(kwargs)) : # can also use the form <componentname>=<number> l = list(self.flat) setcomp = False for i, c in enumerate(self.cnames) : if c in kwargs : if float(l[i]) != float(kwargs[c]) : l[i] = float(kwargs[c]) setcomp = True if setcomp : try : self.assign(l) except : msg = ", ".join(map(lambda x,y:x+"=<"+util.clsname(y)+">", cls.cnames, l)) raise TypeError, "in %s(%s), at least one of the components is of an invalid type, check help(%s) " % (cls.__name__, msg, cls.__name__) return self # API get, actually not faster than pulling self[i] for such a short structure def get(self): """ Wrap the MEulerRotation api get method """ # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... ms = _api.MScriptUtil() l = (0,)*self.size ms.createFromDouble ( *l ) p = ms.asDoublePtr () self.apicls.get(self, p) return tuple([ms.getDoubleArrayItem ( p, i ) for i in xrange(self.size)]) def __contains__(self, value): """ True if at least one of the vector components is equal to the argument """ return value in self.__iter__() def __len__(self): return self.apicls.__len__(self) # common operators without an api equivalent are herited from VectorN # operators using the Maya API when applicable, but that can delegate to VectorN def __eq__(self, other): """ u.__eq__(v) <==> u == v Equivalence test """ if isinstance( other, self.apicls ): return bool(self.apicls.__eq__(self, other)) else : return bool(super(EulerRotation, self).__eq__(other)) def __ne__(self, other): """ u.__ne__(v) <==> u != v Equivalence test """ return (not self.__eq__(other)) def __neg__(self): """ u.__neg__() <==> -u The unary minus operator. Negates the value of each of the components of u """ return self.__class__(self.apicls.__neg__(self)) def __add__(self, other) : """ u.__add__(v) <==> u+v Returns the result of the addition of u and v if v is convertible to a VectorN (element-wise addition), adds v to every component of u if v is a scalar """ try : return self.__class__._convert(self.apicls.__add__(self, other)) except : return self.__class__._convert(super(EulerRotation, self).__add__(other)) def __radd__(self, other) : """ u.__radd__(v) <==> v+u Returns the result of the addition of u and v if v is convertible to a VectorN (element-wise addition), adds v to every component of u if v is a scalar """ try : return self.__class__._convert(self.apicls.__radd__(self, other)) except : return self.__class__._convert(super(EulerRotation, self).__radd__(other)) def __iadd__(self, other): """ u.__iadd__(v) <==> u += v In place addition of u and v, see __add__ """ try : return self.__class__(self.__add__(other)) except : return NotImplemented def __sub__(self, other) : """ u.__sub__(v) <==> u-v Returns the result of the substraction of v from u if v is convertible to a VectorN (element-wise substration), substract v to every component of u if v is a scalar """ try : return self.__class__._convert(self.apicls.__sub__(self, other)) except : return self.__class__._convert(super(EulerRotation, self).__sub__(other)) def __rsub__(self, other) : """ u.__rsub__(v) <==> v-u Returns the result of the substraction of u from v if v is convertible to a VectorN (element-wise substration), replace every component c of u by v-c if v is a scalar """ try : return self.__class__._convert(self.apicls.__rsub__(self, other)) except : return self.__class__._convert(super(EulerRotation, self).__rsub__(other)) def __isub__(self, other): """ u.__isub__(v) <==> u -= v In place substraction of u and v, see __sub__ """ try : return self.__class__(self.__sub__(other)) except : return NotImplemented def __div__(self, other): """ u.__div__(v) <==> u/v Returns the result of the division of u by v if v is convertible to a VectorN (element-wise division), divide every component of u by v if v is a scalar """ try : return self.__class__._convert(self.apicls.__div__(self, other)) except : return self.__class__._convert(super(EulerRotation, self).__div__(other)) def __rdiv__(self, other): """ u.__rdiv__(v) <==> v/u Returns the result of of the division of v by u if v is convertible to a VectorN (element-wise division), invert every component of u and multiply it by v if v is a scalar """ try : return self.__class__._convert(self.apicls.__rdiv__(self, other)) except : return self.__class__._convert(super(EulerRotation, self).__rdiv__(other)) def __idiv__(self, other): """ u.__idiv__(v) <==> u /= v In place division of u by v, see __div__ """ try : return self.__class__(self.__div__(other)) except : return NotImplemented # action depends on second object type def __mul__(self, other) : """ u.__mul__(v) <==> u*v The multiply '*' operator is mapped to the dot product when both objects are Vectors, to the transformation of u by matrix v when v is a MatrixN, to element wise multiplication when v is a sequence, and multiplies each component of u by v when v is a numeric type. """ try : res = self.apicls.__mul__(self, other) except : res = super(EulerRotation, self).__mul__(other) if util.isNumeric(res) : return res else : return self.__class__._convert(res) def __rmul__(self, other): """ u.__rmul__(v) <==> v*u The multiply '*' operator is mapped to the dot product when both objects are Vectors, to the left side multiplication (pre-multiplication) of u by matrix v when v is a MatrixN, to element wise multiplication when v is a sequence, and multiplies each component of u by v when v is a numeric type. """ try : res = self.apicls.__rmul__(self, other) except : res = super(EulerRotation, self).__rmul__(other) if util.isNumeric(res) : return res else : return self.__class__._convert(res) def __imul__(self, other): """ u.__imul__(v) <==> u *= v Valid for EulerRotation * Matrix multiplication, in place transformation of u by Matrix v or EulerRotation by scalar multiplication only """ try : return self.__class__(self.__mul__(other)) except : return NotImplemented # special operators # def __xor__(self, other): # """ u.__xor__(v) <==> u^v # Defines the cross product operator between two 3D vectors, # if v is a MatrixN, u^v is equivalent to u.transformAsNormal(v) """ # if isinstance(other, VectorN) : # return self.cross(other) # elif isinstance(other, MatrixN) : # return self.transformAsNormal(other) # else : # return NotImplemented # def __ixor__(self, other): # """ u.__xor__(v) <==> u^=v # Inplace cross product or transformation by inverse transpose of v is v is a MatrixN """ # try : # return self.__class__(self.__xor__(other)) # except : # return NotImplemented class Quaternion(Matrix): apicls = _api.MQuaternion shape = (4,) cnames = ('x', 'y', 'z', 'w') def __new__(cls, *args, **kwargs): shape = kwargs.get('shape', None) ndim = kwargs.get('ndim', None) size = kwargs.get('size', None) # will default to class constant shape = (4,), so it's just an error check to catch invalid shapes, # as no other option is actually possible on Quaternion, but this method could be used to allow wrapping # of Maya array classes that can have a variable number of elements shape, ndim, size = cls._expandshape(shape, ndim, size) new = cls.apicls.__new__(cls) cls.apicls.__init__(new) return new def __init__(self, *args, **kwargs): """ __init__ method for Quaternion """ cls = self.__class__ if args : # allow both forms for arguments if len(args)==1 and hasattr(args[0], '__iter__') : args = args[0] rotate = getattr(args, 'rotate', None) # TransformationMatrix, Quaternion, EulerRotation api classes can convert to a rotation Quaternion if rotate is not None and not callable(rotate): args = args.rotate self.unit = 'radians' elif len(args) == 4 and isinstance(args[3], (basestring, util.EnumValue)): # isinstance(args[3], EulerRotation.RotationOrder) ) : quat = _api.MQuaternion() quat.assign(EulerRotation(*args, **kwargs)) args = quat # allow to initialize directly from 3 rotations and a rotation order elif len(args) == 2 and isinstance(args[0], VectorN) and isinstance(args[1], float) : # some special init cases are allowed by the api class, want to authorize # Quaternion(Vector axis, float angle) as well as Quaternion(float angle, Vector axis) args = (float(args[1]), Vector(args[0])) # shortcut when a direct api init is possible try : self.assign(args) except : super(Array, self).__init__(*args) if hasattr(cls, 'cnames') and len(set(cls.cnames) & set(kwargs)) : # can also use the form <componentname>=<number> l = list(self.flat) setcomp = False for i, c in enumerate(cls.cnames) : if c in kwargs : if float(l[i]) != float(kwargs[c]) : l[i] = float(kwargs[c]) setcomp = True if setcomp : try : self.assign(l) except : msg = ", ".join(map(lambda x,y:x+"=<"+util.clsname(y)+">", cls.cnames, l)) raise TypeError, "in %s(%s), at least one of the components is of an invalid type, check help(%s) " % (cls.__name__, msg, cls.__name__) # set properties for easy acces to translation / rotation / scale of a MMatrix or derived class # some of these will only yield dependable results if MMatrix is a MTransformationMatrix and some # will always be zero for some classes (ie only rotation has a value on a MQuaternion def _getTranslate(self): return Vector(0.0, 0.0, 0.0) translate = property(_getTranslate, None, None, "The translation expressed in this MMQuaternion, which is always (0.0, 0.0, 0.0)") def _getRotate(self): return self def _setRotate(self, value): self.assign(Quaternion(value)) rotate = property(_getRotate, _setRotate, None, "The rotation expressed in this Quaternion, in transform space") def _getScale(self): return Vector(1.0, 1.0, 1.0) scale = property(_getScale, None, None, "The scale expressed in this Quaternion, which is always (1.0, 1.0, 1.0)") # overloads for assign and get though standard way should be to use the data property # to access stored values def assign(self, value): """ Wrap the Quaternion api assign method """ # api Quaternion assign accepts Matrix, Quaternion and EulerRotation if isinstance(value, Matrix) : value = value.rotate else : if not hasattr(value, '__iter__') : value = (value,) value = self.apicls(*value) self.apicls.assign(self, value) return self # API get, actually not faster than pulling self[i] for such a short structure def get(self): """ Wrap the Quaternion api get method """ # need to keep a ref to the MScriptUtil alive until # all pointers aren't needed... ms = _api.MScriptUtil() l = (0,)*self.size ms.createFromDouble ( *l ) p = ms.asDoublePtr () self.apicls.get(self, p) return tuple([ms.getDoubleArrayItem ( p, i ) for i in xrange(self.size)]) def __getitem__(self,i): return self._getitem(i) # faster to override __getitem__ cause we know Quaternion only has one dimension def _getitem(self, i): """ Get component i value from self """ if hasattr(i, '__iter__') : i = list(i) if len(i) == 1 : i = i[0] else : raise IndexError, "class %s instance %s has only %s dimension(s), index %s is out of bounds" % (util.clsname(self), self, self.ndim, i) if isinstance(i, slice) : try : return list(self)[i] except : raise IndexError, "class %s instance %s is of size %s, index %s is out of bounds" % (util.clsname(self), self, self.size, i) else : if i < 0 : i = self.size + i if i<self.size and not i<0 : if hasattr(self.apicls, '__getitem__') : res = self.apicls.__getitem__(self, i) else : res = list(self)[i] return res else : raise IndexError, "class %s instance %s is of size %s, index %s is out of bounds" % (util.clsname(self), self, self.size, i) # as _api.Vector has no __setitem__ method, so need to reassign the whole Vector def __setitem__(self, i, a): """ Set component i value on self """ v = VectorN(self) v.__setitem__(i, a) self.assign(v) def __iter__(self): for i in range(self.size): yield self[i] def __len__(self): # api incorrectly returns 4. this might make sense if it did not simply return z a second time as the fourth element return self.size # # # TODO : support for optional __iter__ arguments # def __iter__(self, *args, **kwargs): # """ Iterate on the api components """ # return self.apicls.__iter__(self.data) def __contains__(self, value): """ True if at least one of the vector components is equal to the argument """ return value in self.__iter__() class Unit(float): __slots__ = ['unit', 'data', 'value', '_unit'] @classmethod def getUIUnit(cls): """ Returns the global UI units currently in use for that type """ return cls.sUnit(cls.apicls.uiUnit()) @classmethod def setUIUnit(cls, unit=None): """ Sets the global UI units currently to use for that type """ if unit is None : cls.apicls.setUIUnit(cls.apicls.internalUnit()) else : cls.apicls.setUIUnit(cls.kUnit(unit)) @classmethod def getInternalUnit(cls): """ Returns the inernal units currently in use for that type """ return cls.sUnit(cls.apicls.internalUnit()) @classmethod def uiToInternal (cls, value) : d = cls(value, cls.getUIUnit()) return d.asInternalUnit() @classmethod def kUnit(cls, unit=None): """ Converts a string unit name to the internal int unit enum representation """ if unit : return cls.Unit.getIndex(unit) else : return cls.apicls.uiUnit() @classmethod def sUnit(cls, unit=None) : """ Converts an internal int unit enum representation tp the string unit name """ if unit : return cls.Unit.getKey(unit) else : return str(cls.unit[cls.apicls.uiUnit()]) def getUnit(self): """ Returns the units currently in effect for this instance """ return self.__class__.sUnit(self._unit) # def setUnit(self, unit=None) : # """ # Sets the units currently in effect for this instance # """ # self._unit = self.__class__.kUnit(unit) unit = property(getUnit, None, None, "The units currently in effect for this instance") def __new__(cls, value, unit=None) : unit = cls.kUnit(unit) if isinstance(value, cls.apicls): value = getattr(value, AS_UNITS)(unit) elif isinstance(value, cls): value = value.asUnit(unit) #data = cls.apicls(value, unit) # the float representation uses internal units so that arithmetics work #newobj = float.__new__(cls, data.asUnit(cls.apicls.internalUnit())) #newobj = float.__new__(cls, data.asUnit(unit)) newobj = float.__new__(cls, value) #ewobj._data = data newobj._unit = unit newobj._data = cls.apicls(value, unit) return newobj def assign(self, *args): if isinstance (args, self.__class__) : args = (args._data, args._unit) self._data.assign(*args) def __repr__(self) : return 'dt.%s(%s, unit=%r)' % ( self.__class__.__name__, self, self.unit ) def asUnit(self, unit) : # in python2.6/maya2010 'as' becomes a keyword. return getattr( self._data, AS_UNITS )( self.__class__.kUnit(unit) ) # def asUnit(self) : # return self.asUnit(self.unit) def asUIUnit(self) : return self.asUnit(self.__class__.getUIUnit()) def asInternalUnit(self) : return self.asUnit(self.__class__.getInternalUnit()) class Time(Unit): apicls = _api.MTime Unit = _factories.apiClassInfo['MTime']['pymelEnums']['Unit'] class Distance( Unit ) : """ >>> from pymel.core import * >>> import pymel.core.datatypes as dt >>> >>> dt.Distance.getInternalUnit() 'centimeters' >>> dt.Distance.setUIUnit('meters') >>> dt.Distance.getUIUnit() 'meters' >>> d = dt.Distance(12) >>> d.unit 'meters' >>> print d 12.0 >>> print repr(d) dt.Distance(12.0, unit='meters') >>> print d.asUIUnit() 12.0 >>> print d.asInternalUnit() 1200.0 >>> dt.Distance.setUIUnit('centimeters') >>> dt.Distance.getUIUnit() 'centimeters' >>> e = dt.Distance(12) >>> e.unit 'centimeters' >>> print e 12.0 >>> str(e) '12.0' >>> print repr(e) dt.Distance(12.0, unit='centimeters') >>> print e.asUIUnit() 12.0 >>> print e.asInternalUnit() 12.0 >>> f = dt.Distance(12, 'feet') >>> print f 12.0 >>> print repr(f) dt.Distance(12.0, unit='feet') >>> f.unit 'feet' >>> print f.asUIUnit() 365.76 >>> dt.Distance.setUIUnit('meters') >>> dt.Distance.getUIUnit() 'meters' >>> print f.asUIUnit() 3.6576 >>> dt.Distance.getInternalUnit() 'centimeters' >>> print f.asInternalUnit() 365.76 >>> print f.asFeet() 12.0 >>> print f.asMeters() 3.6576 >>> print f.asCentimeters() 365.76 >>> dt.Distance.setUIUnit() >>> dt.Distance.getUIUnit() 'centimeters' """ apicls = _api.MDistance Unit = _factories.apiClassInfo['MDistance']['pymelEnums']['Unit'] def asMillimeter(self) : return self.asUnit('millimeter') def asCentimeters(self) : return self.asUnit('centimeters') def asKilometers(self) : return self.asUnit('kilometers') def asMeters(self) : return self.asUnit('meters') def asInches(self) : return self.asUnit('inches') def asFeet(self) : return self.asUnit('feet') def asYards(self) : return self.asUnit('yards') def asMiles(self) : return self.asUnit('miles') class Angle( Unit ): apicls = _api.MAngle Unit = _factories.apiClassInfo['MAngle']['pymelEnums']['Unit'] def asRadians(self): return self.asUnit('radians') def asDegrees(self): return self.asUnit('degrees') def asAngMinutes(self): return self.asUnit('angMinutes') def asAngSeconds(self): return self.asUnit('angSeconds') class BoundingBox( _api.MBoundingBox): apicls = _api.MBoundingBox __metaclass__ = _factories.MetaMayaTypeWrapper def __init__(self, *args): if len(args) == 2: args = list(args) if not isinstance( args[0], _api.MPoint ): args[0] = Point( args[0] ) if not isinstance( args[1], _api.MPoint ): args[1] = Point( args[1] ) _api.MBoundingBox.__init__(self, *args) def __str__(self): return 'dt.%s(%s,%s)' % (self.__class__.__name__, self.min(), self.max()) def __repr__(self): return str(self) def __getitem__(self,item): if item == 0: return self.min() elif item == 1: return self.max() raise IndexError, "Index out of range" def __melobject__(self): """A flat list of 6 values [minx, miny, minz, maxx, maxy, maxz]""" return list(self.min()) + list(self.max()) repr = __str__ w = property( _factories.wrapApiMethod( _api.MBoundingBox, 'width' ) ) h = property( _factories.wrapApiMethod( _api.MBoundingBox, 'height' ) ) d = property( _factories.wrapApiMethod( _api.MBoundingBox, 'depth' ) ) #_factories.ApiTypeRegister.register( 'MVector', Vector ) #_factories.ApiTypeRegister.register( 'MMatrix', Matrix ) #_factories.ApiTypeRegister.register( 'MPoint', Point ) #_factories.ApiTypeRegister.register( 'MColor', Color ) #_factories.ApiTypeRegister.register( 'MQuaternion', Quaternion ) #_factories.ApiTypeRegister.register( 'MEulerRotation', EulerRotation ) _factories.ApiTypeRegister.register( 'MTime', Time, inCast=lambda x: Time(x)._data ) _factories.ApiTypeRegister.register( 'MDistance', Distance, outCast=lambda instance, result: Distance(result,'centimeters').asUIUnit()) _factories.ApiTypeRegister.register( 'MAngle', Angle, outCast=lambda instance, result: Angle(result,'radians').asUIUnit()) #_floatUpConvertDict = {_api.MFloatArray:_api.MDoubleArray, # _api.MFloatMatrix:_api.MMatrix, # _api.MFloatPoint:_api.MPoint, # _api.MFloatPointArray:_api.MPointArray, # _api.MFloatVector:_api.MVector, # _api.MFloatVectorArray:_api.MVectorArray, # FloatMatrix:Matrix, # FloatPoint:Point, # FloatVector:Vector # } #def _floatUpConvert(input): # """Will convert various Float* objects to their corresponding double object # # ie, api.MFloatMatrix => api.MMatrix, FloatPoint => Point # """ # newClass = _floatUpConvertDict.get(input.__class__) # if newClass: # return newClass(input) # else: # return input def getPlugValue( plug ): """given an MPlug, get its value as a pymel-style object""" #if plug.isArray(): # raise TypeError, "array plugs of this type are not supported" obj = plug.attribute() apiType = obj.apiType() # Float Pairs if apiType in [ _api.MFn.kAttribute2Double, _api.MFn.kAttribute2Float ] : res = [] for i in range(plug.numChildren()): res.append( getPlugValue( plug.child(i) ) ) if isinstance(res[0],Distance): return Vector(res) return res # Integer Groups elif apiType in [ _api.MFn.kAttribute2Short, _api.MFn.kAttribute2Int, _api.MFn.kAttribute3Short, _api.MFn.kAttribute3Int ] : res = [] for i in range(plug.numChildren()): res.append( getPlugValue( plug.child(i) ) ) return res # Float Groups elif apiType in [ _api.MFn.kAttribute3Double, _api.MFn.kAttribute3Float, _api.MFn.kAttribute4Double ] : res = [] for i in range(plug.numChildren()): res.append( getPlugValue( plug.child(i) ) ) if isinstance(res[0],Distance): return Vector(res) elif _api.MFnAttribute(obj).isUsedAsColor(): return Color(res) return res # Compound elif apiType in [ _api.MFn.kCompoundAttribute ] : res = [] for i in range(plug.numChildren()): res.append( getPlugValue( plug.child(i) ) ) return tuple(res) # Distance elif apiType in [ _api.MFn.kDoubleLinearAttribute, _api.MFn.kFloatLinearAttribute ] : val = plug.asMDistance() unit = _api.MDistance.uiUnit() # as becomes a keyword in python 2.6 return Distance( getattr(val, AS_UNITS)( unit ), unit ) # Angle elif apiType in [ _api.MFn.kDoubleAngleAttribute, _api.MFn.kFloatAngleAttribute ] : val = plug.asMAngle() unit = _api.MAngle.uiUnit() # as becomes a keyword in python 2.6 return Angle( getattr(val, AS_UNITS), unit ) # Time elif apiType == _api.MFn.kTimeAttribute: val = plug.asMTime() unit = _api.MTime.uiUnit() # as becomes a keyword in python 2.6 return Time( getattr(val, AS_UNITS), unit ) elif apiType == _api.MFn.kNumericAttribute: nAttr = _api.MFnNumericAttribute(obj) dataType = nAttr.unitType() if dataType == _api.MFnNumericData.kBoolean: return plug.asBool() elif dataType in [ _api.MFnNumericData.kShort, _api.MFnNumericData.kInt, _api.MFnNumericData.kLong, _api.MFnNumericData.kByte] : return plug.asInt() elif dataType in [ _api.MFnNumericData.kFloat, _api.MFnNumericData.kDouble, _api.MFnNumericData.kAddr] : return plug.asDouble() raise "%s: unknown numeric attribute type: %s" % (plug.partialName(True, True, True, False, True, True), dataType) elif apiType == _api.MFn.kEnumAttribute: # TODO : use EnumValue class? return plug.asInt() elif apiType == _api.MFn.kTypedAttribute: tAttr = _api.MFnTypedAttribute( obj ) dataType = tAttr.attrType() if dataType == _api.MFnData.kInvalid: # 0 return None elif dataType == _api.MFnData.kNumeric: # 1 # all of the dynamic mental ray attributes fail here, but i have no idea why they are numeric attrs and not message attrs. # cmds.getAttr returns None, so we will too. try: dataObj = plug.asMObject() except: return try: numFn = _api.MFnNumericData( dataObj ) except RuntimeError: if plug.isArray(): raise TypeError, "%s: numeric arrays are not supported" % plug.partialName(True, True, True, False, True, True) else: raise TypeError, "%s: attribute type is numeric, but its data cannot be interpreted numerically" % plug.partialName(True, True, True, False, True, True) dataType = numFn.numericType() if dataType == _api.MFnNumericData.kBoolean: return plug.asBool() elif dataType in [ _api.MFnNumericData.kShort, _api.MFnNumericData.kInt, _api.MFnNumericData.kLong, _api.MFnNumericData.kByte] : return plug.asInt() elif dataType in [ _api.MFnNumericData.kFloat, _api.MFnNumericData.kDouble, _api.MFnNumericData.kAddr] : return plug.asDouble() elif dataType == _api.MFnNumericData.k2Short : ptr1 = _api.SafeApiPtr('short') ptr2 = _api.SafeApiPtr('short') numFn.getData2Short(ptr1(), ptr2()) return ( ptr1.get(), ptr2.get() ) elif dataType in [ _api.MFnNumericData.k2Int, _api.MFnNumericData.k2Long ]: ptr1 = _api.SafeApiPtr('int') ptr2 = _api.SafeApiPtr('int') numFn.getData2Int(ptr1(), ptr2()) return ( ptr1.get(), ptr2.get() ) elif dataType == _api.MFnNumericData.k2Float : ptr1 = _api.SafeApiPtr('float') ptr2 = _api.SafeApiPtr('float') numFn.getData2Float(ptr1(), ptr2()) return ( ptr1.get(), ptr2.get() ) elif dataType == _api.MFnNumericData.k2Double : ptr1 = _api.SafeApiPtr('double') ptr2 = _api.SafeApiPtr('double') numFn.getData2Double(ptr1(), ptr2()) return ( ptr1.get(), ptr2.get() ) elif dataType == _api.MFnNumericData.k3Float: ptr1 = _api.SafeApiPtr('float') ptr2 = _api.SafeApiPtr('float') ptr3 = _api.SafeApiPtr('float') numFn.getData3Float(ptr1(), ptr2(), ptr3()) return ( ptr1.get(), ptr2.get(), ptr3.get() ) elif dataType == _api.MFnNumericData.k3Double: ptr1 = _api.SafeApiPtr('double') ptr2 = _api.SafeApiPtr('double') ptr3 = _api.SafeApiPtr('double') numFn.getData3Double(ptr1(), ptr2(), ptr3()) return ( ptr1.get(), ptr2.get(), ptr3.get() ) elif dataType == _api.MFnNumericData.kChar : return plug.asChar() raise TypeError, "%s: Unsupported numeric attribute: %s" % (plug.partialName(True, True, True, False, True, True),dataType) elif dataType == _api.MFnData.kString: # 4 return plug.asString() elif dataType == _api.MFnData.kMatrix : # 5 return Matrix( _api.MFnMatrixData( plug.asMObject() ).matrix() ) elif dataType == _api.MFnData.kStringArray : # 6 try: dataObj = plug.asMObject() except RuntimeError: return [] array = _api.MFnStringArrayData( dataObj ).array() return [ array[i] for i in range(array.length()) ] elif dataType == _api.MFnData.kDoubleArray : # 7 try: dataObj = plug.asMObject() except RuntimeError: return [] array = _api.MFnDoubleArrayData( dataObj ).array() return [ array[i] for i in range(array.length()) ] elif dataType == _api.MFnData.kIntArray : # 8 try: dataObj = plug.asMObject() except RuntimeError: return [] array = _api.MFnIntArrayData( dataObj ).array() return [ array[i] for i in range(array.length()) ] elif dataType == _api.MFnData.kPointArray : # 9 try: dataObj = plug.asMObject() except RuntimeError: return [] array = _api.MFnPointArrayData( dataObj ).array() return [ Point(array[i]) for i in range(array.length()) ] elif dataType == _api.MFnData.kVectorArray : # 10 try: dataObj = plug.asMObject() except RuntimeError: return [] array = _api.MFnVectorArrayData( dataObj ).array() return [ Vector(array[i]) for i in range(array.length()) ] # this block crashes maya under certain circumstances # elif dataType == _api.MFnData.kComponentList : # 11 # try: # dataObj = plug.asMObject() # except RuntimeError: # return [] # array = _api.MFnComponentListData( dataObj ) # return array # #return [ Vector(array[i]) for i in range(array.length()) ] raise TypeError, "%s: Unsupported typed attribute: %s" % (plug.partialName(True, True, True, False, True, True),dataType) raise TypeError, "%s: Unsupported Type: %s" % (plug.partialName(True, True, True, False, True, True), _factories.apiEnumsToApiTypes.get( apiType, apiType )) def _testMVector() : print "Vector class:", dir(Vector) u = Vector() print u print "Vector instance:", dir(u) print repr(u) print Vector.__readonly__ print Vector.__slots__ print Vector.shape print Vector.ndim print Vector.size print u.shape print u.ndim print u.size # should fail u.shape = 2 u.assign(Vector(4, 5, 6)) print repr(u) #Vector([4.0, 5.0, 6.0]) u = Vector(1, 2, 3) print repr(u) # Vector([1.0, 2.0, 3.0]) print len(u) # 3 # inherits from VectorN --> Array print isinstance(u, VectorN) # True print isinstance(u, Array) # True # as well as _api.Vector print isinstance(u, _api.MVector) # True # accepted directly by API methods M = _api.MTransformationMatrix() M.setTranslation ( u, _api.MSpace.kWorld ) # need conversion on the way back though u = Vector(M.getTranslation ( _api.MSpace.kWorld )) print repr(u) # Vector([1.0, 2.0, 3.0]) u = Vector(x=1, y=2, z=3) print repr(u) # Vector([1.0, 2.0, 3.0]) u = Vector([1, 2], z=3) print repr(u) # Vector([1.0, 2.0, 3.0]) u = Vector(_api.MPoint(1, 2, 3)) print repr(u) # Vector([1.0, 2.0, 3.0]) print "u = Vector(VectorN(1, 2, 3))" u = Vector(VectorN(1, 2, 3)) print repr(u) # Vector([1.0, 2.0, 3.0]) u = Vector(1) print repr(u) # Vector([1.0, 1.0, 1.0]) u = Vector(1,2) print repr(u) # Vector([1.0, 2.0, 0.0]) u = Vector(VectorN(1, shape=(2,))) print repr(u) # Vector([1.0, 1.0, 0.0]) u = Vector(Point(1, 2, 3)) print repr(u) # Vector([1.0, 2.0, 3.0]) u = Vector(Point(1, 2, 3, 1), y=20, z=30) print repr(u) # Vector([1.0, 20.0, 30.0]) # should fail print "Vector(VectorN(1, 2, 3, 4))" try : u = Vector(VectorN(1, 2, 3, 4)) except : print "will raise ValueError: could not cast [1, 2, 3, 4] to Vector of size 3, some data would be lost" print u.get() # (1.0, 20.0, 30.0) print u[0] 1.0 u[0] = 10 print repr(u) # Vector([10.0, 20.0, 30.0]) print (10 in u) # True print list(u) # [10.0, 20.0, 30.0] u = Vector.xAxis v = Vector.yAxis print Vector.xAxis print str(Vector.xAxis) print unicode(Vector.xAxis) print repr(Vector.xAxis) print "u = Vector.xAxis:" print repr(u) # Vector([1.0, 0.0, 0.0]) print "v = Vector.yAxis:" print repr(v) # Vector([0.0, 1.0, 0.0]) n = u ^ v print "n = u ^ v:" print repr(n) # Vector([0.0, 0.0, 1.0]) print "n.x=%s, n.y=%s, n.z=%s" % (n.x, n.y, n.z) # n.x=0.0, n.y=0.0, n.z=1.0 n = u ^ VectorN(v) print "n = u ^ VectorN(v):" print repr(n) # Vector([0.0, 0.0, 1.0]) n = u ^ [0, 1, 0] print "n = u ^ [0, 1, 0]:" print repr(n) # Vector([0.0, 0.0, 1.0]) n[0:2] = [1, 1] print "n[0:2] = [1, 1]:" print repr(n) # Vector([1.0, 1.0, 1.0]) print "n = n * 2 :" n = n*2 print repr(n) # Vector([2.0, 2.0, 2.0]) print "n = n * [0.5, 1.0, 2.0]:" n = n*[0.5, 1.0, 2.0] print repr(n) # Vector([1.0, 2.0, 4.0]) print "n * n :" print n * n # 21.0 print repr(n.clamp(1.0, 2.0)) # Vector([1.0, 2.0, 2.0]) print repr(-n) # Vector([-1.0, -2.0, -4.0]) w = u + v print repr(w) # Vector([1.0, 1.0, 0.0]) p = Point(1, 2, 3) q = u + p print repr(q) # Point([2.0, 2.0, 3.0, 1.0]) q = p + u print repr(q) # Point([2.0, 2.0, 3.0, 1.0]) print repr(p+q) # Point([3.0, 4.0, 6.0, 1.0]) w = u + VectorN(1, 2, 3, 4) print repr(w) # VectorN([2.0, 2.0, 3.0, 4]) print repr(u+2) # Vector([3.0, 2.0, 2.0]) print repr(2+u) # Vector([3.0, 2.0, 2.0]) print repr(p+2) # Point([3.0, 4.0, 5.0, 1.0]) print repr(2+p) # Point([3.0, 4.0, 5.0, 1.0]) print repr(p + u) # Point([2.0, 2.0, 3.0, 1.0]) print repr(VectorN(1, 2, 3, 4) + u) # VectorN([2.0, 2.0, 3.0, 4]) print repr([1, 2, 3] + u) # Vector([2.0, 2.0, 3.0]) u = Vector(1, 2, 3) print repr(u) # Vector([1.0, 2.0, 3.0]) print u.length() # 3.74165738677 print length(u) # 3.74165738677 print length([1, 2, 3]) # 3.74165738677 print length(VectorN(1, 2, 3)) # 3.74165738677 print VectorN(1, 2, 3).length() # 3.74165738677 print length(VectorN(1, 2, 3, 4)) # 5.47722557505 print VectorN(1, 2, 3, 4).length() # 5.47722557505 print length(1) # 1.0 print length([1, 2]) # 2.2360679775 print length([1, 2, 3]) # 3.74165738677 print length([1, 2, 3, 4]) # 5.47722557505 print length([1, 2, 3, 4], 0) # 5.47722557505 print length([1, 2, 3, 4], (0,)) # 5.47722557505 print length([[1, 2], [3, 4]], 1) # [3.16227766017, 4.472135955] # should fail try : print length([1, 2, 3, 4], 1) except : print "Will raise ValueError, \"axis 0 is the only valid axis for a Vector, 1 invalid\"" u = Vector(1, 2, 3) print repr(u) # Vector([1.0, 2.0, 3.0]) print u.sqlength() # 14 print repr(u.normal()) # Vector([0.267261241912, 0.534522483825, 0.801783725737]) u.normalize() print repr(u) # Vector([0.267261241912, 0.534522483825, 0.801783725737]) u = Vector(1, 2, 3) print repr(u) # Vector([1.0, 2.0, 3.0]) w = u + [0.01, 0.01, 0.01] print repr(w) # Vector([1.01, 2.01, 3.01]) print (u == u) # True print (u == w) # False print (u == Vector(1.0, 2.0, 3.0)) # True print (u == [1.0, 2.0, 3.0]) # False print (u == Point(1.0, 2.0, 3.0)) # False print u.isEquivalent([1.0, 2.0, 3.0]) # True print u.isEquivalent(Vector(1.0, 2.0, 3.0)) # True print u.isEquivalent(Point(1.0, 2.0, 3.0)) # True print u.isEquivalent(w) # False print u.isEquivalent(w, 0.1) # True u = Vector(1, 0, 0) print repr(u) # Vector([1.0, 0.0, 0.0]) v = Vector(0.707, 0, -0.707) print repr(v) # Vector([0.707, 0.0, -0.707]) print repr(axis(u, v)) # Vector([-0.0, 0.707, 0.0]) print repr(u.axis(v)) # Vector([-0.0, 0.707, 0.0]) print repr(axis(VectorN(u), VectorN(v))) # VectorN([-0.0, 0.707, 0.0]) print repr(axis(u, v, normalize=True)) # Vector([-0.0, 1.0, 0.0]) print repr(v.axis(u, normalize=True)) # Vector([-0.0, -1.0, 0.0]) print repr(axis(VectorN(u), VectorN(v), normalize=True)) # VectorN([-0.0, 1.0, 0.0]) print angle(u,v) # 0.785398163397 print v.angle(u) # 0.785398163397 print angle(VectorN(u), VectorN(v)) # 0.785398163397 print cotan(u, v) # 1.0 print repr(u.rotateTo(v)) # Quaternion([-0.0, 0.382683432365, 0.0, 0.923879532511]) print repr(u.rotateBy(u.axis(v), u.angle(v))) # Vector([0.707106781187, 0.0, -0.707106781187]) q = Quaternion([-0.0, 0.382683432365, 0.0, 0.923879532511]) print repr(u.rotateBy(q)) # Vector([0.707106781187, 0.0, -0.707106781187]) print u.distanceTo(v) # 0.765309087885 print u.isParallel(v) # False print u.isParallel(2*u) # True print repr(u.blend(v)) # Vector([0.8535, 0.0, -0.3535]) print "end tests Vector" def _testMPoint() : print "Point class", dir(Point) print hasattr(Point, 'data') p = Point() print repr(p) # Point([0.0, 0.0, 0.0]) print "Point instance", dir(p) print hasattr(p, 'data') print repr(p.data) # <maya.OpenMaya.Point; proxy of <Swig Object of type 'Point *' at 0x84a1270> > p = Point(1, 2, 3) print repr(p) # Point([1.0, 2.0, 3.0]) v = Vector(p) print repr(v) # Vector([1.0, 2.0, 3.0]) V = VectorN(p) print repr(V) # VectorN([1.0, 2.0, 3.0, 1.0]) print list(p) # [1.0, 2.0, 3.0] print len(p) # 3 print p.size # 4 print p.x, p.y, p.z, p.w # 1.0 2.0 3.0 1.0 print p[0], p[1], p[2], p[3] # 1.0 2.0 3.0 1.0 p.get() # 1.0 2.0 3.0 1.0 # accepted by api q = _api.MPoint() print q.distanceTo(p) # 3.74165738677 # support for non cartesian points still there p = Point(1, 2, 3, 2) print repr(p) # Point([1.0, 2.0, 3.0, 2.0]) v = Vector(p) print repr(v) # Vector([0.5, 1.0, 1.5]) V = VectorN(p) print repr(V) # VectorN([1.0, 2.0, 3.0, 2.0]) print list(p) # [1.0, 2.0, 3.0, 2.0] print len(p) # 4 print p.size # 4 print p.x, p.y, p.z, p.w # 1.0 2.0 3.0 2.0 print p[0], p[1], p[2], p[3] # 1.0 2.0 3.0 2.0 p.get() # 1.0 2.0 3.0 2.0 # accepted by api q = _api.MPoint() print q.distanceTo(p) # 1.87082869339 p = Point(_api.MPoint()) print repr(p) # Point([0.0, 0.0, 0.0]) p = Point(1) print repr(p) # Point([1.0, 1.0, 1.0]) p = Point(1, 2) print repr(p) # Point([1.0, 2.0, 0.0]) p = Point(1, 2, 3) print repr(p) # Point([1.0, 2.0, 3.0]) p = Point(_api.MPoint(1, 2, 3)) print repr(p) # Point([1.0, 2.0, 3.0]) p = Point(VectorN(1, 2)) print repr(p) # Point([1.0, 2.0, 0.0]) p = Point(Vector(1, 2, 3)) print repr(p) # Point([1.0, 2.0, 3.0]) p = Point(_api.MVector(1, 2, 3)) print repr(p) # Point([1.0, 2.0, 3.0]) p = Point(VectorN(1, 2, 3, 4)) print repr(p) # Point([1.0, 2.0, 3.0, 4.0]) print repr(Vector(p)) # Vector([0.25, 0.5, 0.75]) print repr(VectorN(p)) # VectorN([1.0, 2.0, 3.0, 4.0]) p = Point(p, w=1) print repr(p) # Point([1.0, 2.0, 3.0]) print repr(Vector(p)) # Vector([1.0, 2.0, 3.0]) print repr(VectorN(p)) # VectorN([1.0, 2.0, 3.0, 1.0]) p = Point.origin print repr(p) # Point([0.0, 0.0, 0.0]) p = Point.xAxis print repr(p) # Point([1.0, 0.0, 0.0]) p = Point(1, 2, 3) print repr(p) # Point([1.0, 2.0, 3.0]) print repr(p + Vector([1, 2, 3])) # Point([2.0, 4.0, 6.0]) print repr(p + Point([1, 2, 3])) # Point([2.0, 4.0, 6.0]) print repr(p + [1, 2, 3]) # Point([2.0, 4.0, 6.0]) print repr(p + [1, 2, 3, 1]) # Point([2.0, 4.0, 6.0]) print repr(p + Point([1, 2, 3, 1])) # Point([2.0, 4.0, 6.0]) print repr(p + [1, 2, 3, 2]) # Point([2.0, 4.0, 6.0, 3.0]) TODO : convert to Point always? print repr(p + Point([1, 2, 3, 2])) # Point([1.5, 3.0, 4.5]) print repr(Vector([1, 2, 3]) + p) # Point([2.0, 4.0, 6.0]) print repr(Point([1, 2, 3]) + p) # Point([2.0, 4.0, 6.0]) print repr([1, 2, 3] + p) # Point([2.0, 4.0, 6.0]) print repr([1, 2, 3, 1] + p) # Point([2.0, 4.0, 6.0]) print repr(Point([1, 2, 3, 1]) + p) # Point([2.0, 4.0, 6.0]) print repr([1, 2, 3, 2] + p) # Point([2.0, 4.0, 6.0, 3.0]) print repr(Point([1, 2, 3, 2]) + p) # Point([1.5, 3.0, 4.5]) # various operation, on cartesian and non cartesian points print "p = Point(1, 2, 3)" p = Point(1, 2, 3) print repr(p) # Point([1.0, 2.0, 3.0]) print "p/2" print repr(p/2) # Point([0.5, 1.0, 1.5]) print "p*2" print repr(p*2) # Point([2.0, 4.0, 6.0]) print "q = Point(0.25, 0.5, 1.0)" q = Point(0.25, 0.5, 1.0) print repr(q) # Point([0.25, 0.5, 1.0]) print repr(q+2) # Point([2.25, 2.5, 3.0]) print repr(q/2) # Point([0.125, 0.25, 0.5]) print repr(p+q) # Point([1.25, 2.5, 4.0]) print repr(p-q) # Vector([0.75, 1.5, 2.0]) print repr(q-p) # Vector([-0.75, -1.5, -2.0]) print repr(p-(p-q)) # Point([0.25, 0.5, 1.0]) print repr(Vector(p)*Vector(q)) # 4.25 print repr(p*q) # 4.25 print repr(p/q) # Point([4.0, 4.0, 3.0]) print "p = Point(1, 2, 3)" p = Point(1, 2, 3) print repr(p) # Point([1.0, 2.0, 3.0]) print "p/2" print repr(p/2) # Point([0.5, 1.0, 1.5]) print "p*2" print repr(p*2) # Point([2.0, 4.0, 6.0]) print "q = Point(0.25, 0.5, 1.0, 0.5)" q = Point(0.25, 0.5, 1.0, 0.5) print repr(q) # Point([0.25, 0.5, 1.0, 0.5]) r = q.deepcopy() print repr(r) # Point([0.25, 0.5, 1.0, 0.5]) print repr(r.cartesianize()) # Point([0.5, 1.0, 2.0]) print repr(r) # Point([0.5, 1.0, 2.0]) print repr(q) # Point([0.25, 0.5, 1.0, 0.5]) print repr(q.cartesian()) # Point([0.5, 1.0, 2.0]) r = q.deepcopy() print repr(r) # Point([0.25, 0.5, 1.0, 0.5]) print repr(r.rationalize()) # Point([0.5, 1.0, 2.0, 0.5]) print repr(r) # Point([0.5, 1.0, 2.0, 0.5]) print repr(q.rational()) # Point([0.5, 1.0, 2.0, 0.5]) r = q.deepcopy() print repr(r.homogenize()) # Point([0.125, 0.25, 0.5, 0.5]) print repr(r) # Point([0.125, 0.25, 0.5, 0.5]) print repr(q.homogen()) # Point([0.125, 0.25, 0.5, 0.5]) print repr(q) # Point([0.25, 0.5, 1.0, 0.5]) print Vector(q) # [0.5, 1.0, 2.0] print Vector(q.cartesian()) # [0.5, 1.0, 2.0] # ignore w print "q/2" print repr(q/2) # Point([0.125, 0.25, 0.5, 0.5]) print "q*2" print repr(q*2) # Point([0.5, 1.0, 2.0, 0.5]) print repr(q+2) # cartesianize is done by Vector add # Point([2.5, 3.0, 4.0]) print repr(q) # Point([0.25, 0.5, 1.0, 0.5]) print repr(p+Vector(1, 2, 3)) # Point([2.0, 4.0, 6.0]) print repr(q+Vector(1, 2, 3)) # Point([1.5, 3.0, 5.0]) print repr(q.cartesian()+Vector(1, 2, 3)) # Point([1.5, 3.0, 5.0]) print repr(p-q) # Vector([0.5, 1.0, 1.0]) print repr(p-q.cartesian()) # Vector([0.5, 1.0, 1.0]) print repr(q-p) # Vector([-0.5, -1.0, -1.0]) print repr(p-(p-q)) # Point([0.5, 1.0, 2.0]) print repr(Vector(p)*Vector(q)) # 4.25 print repr(p*q) # 4.25 print repr(p/q) # need explicit homogenize as division not handled by api # Point([4.0, 4.0, 3.0, 2.0]) TODO : what do we want here ? # Vector([2.0, 2.0, 1.5]) # additionnal methods print "p = Point(x=1, y=2, z=3)" p = Point(x=1, y=2, z=3) print p.length() # 3.74165738677 print p[:1].length() # 1.0 print p[:2].length() # 2.2360679775 print p[:3].length() # 3.74165738677 p = Point(1.0, 0.0, 0.0) q = Point(0.707, 0.0, -0.707) print repr(p) # Point([1.0, 0.0, 0.0, 1.0]) print repr(q) # Point([0.707, 0.0, -0.707, 1.0]) print repr(q-p) # Vector([-0.293, 0.0, -0.707]) print repr(axis(Point.origin, p, q)) # Vector([-0.0, 0.707, 0.0]) print repr(Point.origin.axis(p, q)) # Vector([-0.0, 0.707, 0.0]) print repr(Point.origin.axis(q, p)) # Vector([0.0, -0.707, 0.0]) print angle(Point.origin, p, q) # 0.785398163397 print angle(Point.origin, q, p) # 0.785398163397 print Point.origin.angle(p, q) # 0.785398163397 print p.distanceTo(q) # 0.765309087885 print (q-p).length() # 0.765309087885 print cotan(Point.origin, p, q) # 1.0 # obviously True print planar(Point.origin, p, q) # True r = center(Point.origin, p, q) print repr(r) # Point([0.569, 0.0, -0.235666666667, 1.0]) print planar(Point.origin, p, q, r) # True print planar(Point.origin, p, q, r+Vector(0.0, 0.1, 0.0)) # False print bWeights(r, Point.origin, p, q) # (0.33333333333333337, 0.33333333333333331, 0.33333333333333343) p = Point([0.33333, 0.66666, 1.333333, 0.33333]) print repr(round(p, 3)) # Point([0.333, 0.667, 1.333, 0.333]) print "end tests Point" def _testMColor() : print "Color class", dir(Color) print hasattr(Color, 'data') c = Color() print repr(c) # Color([0.0, 0.0, 0.0, 1.0]) print "Color instance", dir(c) print hasattr(c, 'data') print repr(c.data) # Color([0.0, 0.0, 0.0, 1.0]) c = Color(_api.MColor()) print repr(c) # Color([0.0, 0.0, 0.0, 1.0]) # using api convetion of single value would mean alpha # instead of VectorN convention of filling all with value # which would yield # Color([0.5, 0.5, 0.5, 0.5]) instead # This would break coerce behavior for Color print "c = Color(0.5)" c = Color(0.5) print repr(c) # Color([0.5, 0.5, 0.5, 0.5]) print "c = round(Color(128, quantize=255), 2)" c = Color(128, quantize=255) print repr(c) # Color([0.501999974251, 0.501999974251, 0.501999974251, 0.501999974251]) c = Color(255, 128, b=64, a=32, quantize=255) print repr(c) # Color([1.0 0.501999974251 0.250999987125 0.125490196078]) print "c = Color(1, 1, 1)" c = Color(1, 1, 1) print repr(c) # Color([1.0, 1.0, 1.0, 1.0]) print "c = round(Color(255, 0, 255, g=128, quantize=255, mode='rgb'), 2)" c = round(Color(255, 0, 255, g=128, quantize=255, mode='rgb'), 2) print repr(c) # Color([1.0, 0.5, 1.0, 1.0]) print "c = round(Color(255, b=128, quantize=255, mode='rgb'), 2)" c = round(Color(255, b=128, quantize=255, mode='rgb'), 2) print repr(c) # Color([1.0, 1.0, 0.5, 1.0]) print "c = Color(1, 0.5, 2, 0.5)" c = Color(1, 0.5, 2, 0.5) print repr(c) # Color([1.0, 0.5, 2.0, 0.5]) print "c = Color(0, 65535, 65535, quantize=65535, mode='hsv')" c = Color(0, 65535, 65535, quantize=65535, mode='hsv') print repr(c) # Color([1.0, 0.0, 0.0, 1.0]) print "c.rgb" print repr(c.rgb) # (1.0, 0.0, 0.0) print "c.hsv" print repr(c.hsv) # (0.0, 1.0, 1.0) d = Color(c, v=0.5, mode='hsv') print repr(d) # Color([0.5, 0.0, 0.0, 1.0]) print repr(d.hsv) # (0.0, 1.0, 0.5) print "c = Color(Color.blue, v=0.5)" c = Color(Color.blue, v=0.5) print repr(c) # Color([0.0, 0.0, 0.5, 1.0]) print "c.hsv" print c.hsv # (0.66666666666666663, 1.0, 0.5) c.r = 1.0 print repr(c) # Color([1.0, 0.0, 0.5, 1.0]) print "c.hsv" print c.hsv # (0.91666666666666663, 1.0, 1.0) print "c = Color(1, 0.5, 2, 0.5).clamp()" c = Color(1, 0.5, 2, 0.5).clamp() print repr(c) # Color([1.0, 0.5, 1.0, 0.5]) print c.hsv # (0.83333333333333337, 0.5, 1.0) print "Color(c, v=0.5)" d = Color(c, v=0.5) print repr(d) # Color([0.5, 0.25, 0.5, 0.5]) print "d.hsv" print d.hsv # (0.83333333333333337, 0.5, 0.5) print "c = Color(0.0, 0.5, 1.0, 0.5)" c = Color(0.0, 0.5, 1.0, 0.5) print repr(c) # Color(0.0, 0.5, 1.0, 0.5) print "d = c.gamma(2.0)" d = c.gamma(2.0) print repr(d) # Color([0.0, 0.25, 1.0, 0.5]) print "c = Color.red.blend(Color.blue, 0.5)" c = Color.red.blend(Color.blue, 0.5) print repr(c) # Color([0.5, 0.0, 0.5, 1.0]) print c.hsv # (0.83333333333333337, 1.0, 0.5) c = Color.red.hsvblend(Color.blue, 0.5) print repr(c) # Color([1.0, 0.0, 1.0, 1.0]) print c.hsv # (0.83333333333333337, 1.0, 1.0) print "c = Color(0.25, 0.5, 0.75, 0.5)" c = Color(0.25, 0.5, 0.75, 0.5) print repr(c) # Color([0.25, 0.5, 0.75, 0.5]) print "d = Color.black" d = Color.black print repr(d) # Color([0.0, 0.0, 0.0, 1.0]) print "c.over(d)" print repr(c.over(d)) # Color([0.125, 0.25, 0.375, 1.0]) print "d.over(c)" print repr(d.over(c)) # Color([0.0, 0.0, 0.0, 0.5]) print "c.premult()" print repr(c.premult()) # Color([0.125, 0.25, 0.375, 1.0]) # herited from Vector print "c = Color(0.25, 0.5, 1.0, 1.0)" c = Color(0.25, 0.5, 1.0, 1.0) print repr(c) # Color([0.25, 0.5, 1.0, 1.0]) print "d = Color(2.0, 1.0, 0.5, 0.25)" d = Color(2.0, 1.0, 0.5, 0.25) print repr(d) # Color([2.0, 1.0, 0.5, 0.25]) print "-c" print repr(-c) # Color([-0.25, -0.5, -1.0, 1.0]) print "e = c*d" e = c*d print repr(e) # Color([0.5, 0.5, 0.5, 0.25]) print "e + 2" print repr(e+2) # Color([2.5, 2.5, 2.5, 0.25]) print "e * 2.0" # mult by scalar float is defined in api for colors and also multiplies alpha print repr(e*2.0) # Color([1.0, 1.0, 1.0, 0.5]) print "e / 2.0" # as is divide, that ignores alpha now for some reason print repr(e/2.0) # Color([0.25, 0.25, 0.25, 0.25]) print "e+Vector(1, 2, 3)" print repr(e+Vector(1, 2, 3)) # Color([1.5, 2.5, 3.5, 0.25]) # how to handle operations on colors ? # here behaves like api but does it make any sense # for colors as it is now ? print "c+c" print repr(c+c) # Color([0.5, 1.0, 2.0, 1.0]) print "c+d" print repr(c+d) # Color([2.25, 1.5, 1.5, 1.0]) print "d-c" print repr(d-c) # Color([1.75, 0.5, -0.5, 0.25]) print "end tests Color" def _testMMatrix() : print "Matrix class", dir(Matrix) m = Matrix() print m.formated() #[[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]] print m[0, 0] # 1.0 print repr(m[0:2, 0:3]) # [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] print m(0, 0) # 1.0 print "Matrix instance:", dir(m) print Matrix.__readonly__ print Matrix.__slots__ print Matrix.shape print Matrix.ndim print Matrix.size print m.shape print m.ndim print m.size # should fail m.shape = (4, 4) m.shape = 2 print dir(Space) m = Matrix.identity # inherits from MatrixN --> Array print isinstance(m, MatrixN) # True print isinstance(m, Array) # True # as well as _api.Matrix print isinstance(m, _api.MMatrix) # True # accepted directly by API methods n = _api.MMatrix() m = n.setToProduct(m, m) print repr(m) print repr(n) # inits m = Matrix(range(16)) print m.formated() #[[0.0, 1.0, 2.0, 3.0], # [4.0, 5.0, 6.0, 7.0], # [8.0, 9.0, 10.0, 11.0], # [12.0, 13.0, 14.0, 15.0]] M = Array(range(16), shape=(8, 2)) m = Matrix(M) print m.formated() #[[0.0, 1.0, 2.0, 3.0], # [4.0, 5.0, 6.0, 7.0], # [8.0, 9.0, 10.0, 11.0], # [12.0, 13.0, 14.0, 15.0]] M = MatrixN(range(9), shape=(3, 3)) m = Matrix(M) print m.formated() #[[0.0, 1.0, 2.0, 0.0], # [3.0, 4.0, 5.0, 0.0], # [6.0, 7.0, 8.0, 0.0], # [0.0, 0.0, 0.0, 1.0]] # inherits from MatrixN --> Array print isinstance(m, MatrixN) # True print isinstance(m, Array) # True # as well as _api.Matrix print isinstance(m, _api.MMatrix) # True # accepted directly by API methods n = _api.MMatrix() m = n.setToProduct(m, m) print repr(m) print repr(n) t = _api.MTransformationMatrix() t.setTranslation ( Vector(1, 2, 3), _api.MSpace.kWorld ) m = Matrix(t) print m.formated() #[[1.0, 0.0, 0.0, 0.0], # [0.0, 1.0, 0.0, 0.0], # [0.0, 0.0, 1.0, 0.0], # [1.0, 2.0, 3.0, 1.0]] m = Matrix(m, a30=10) print m.formated() #[[1.0, 0.0, 0.0, 0.0], # [0.0, 1.0, 0.0, 0.0], # [0.0, 0.0, 1.0, 0.0], # [10.0, 2.0, 3.0, 1.0]] # should fail print "Matrix(range(20)" try : m = Matrix(range(20)) print m.formated() except : print "will raise ValueError: cannot initialize a Matrix of shape (4, 4) from (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), some information would be lost, use an explicit resize or trim" m = Matrix.identity M = m.trimmed(shape=(3, 3)) print repr(M) # MatrixN([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]) print M.formated() #[[1.0, 0.0, 0.0], # [0.0, 1.0, 0.0], # [0.0, 0.0, 1.0]] try: m.trim(shape=(3, 3)) except : print "will raise TypeError: new shape (3, 3) is not compatible with class Matrix" print m.nrow # 4 print m.ncol # 4 # should fail try : m.nrow = 3 except : print "will raise TypeError: new shape (3, 4) is not compatible with class Matrix" print list(m.row) # [Array([1.0, 0.0, 0.0, 0.0]), Array([0.0, 1.0, 0.0, 0.0]), Array([0.0, 0.0, 1.0, 0.0]), Array([0.0, 0.0, 0.0, 1.0])] print list(m.col) # [Array([1.0, 0.0, 0.0, 0.0]), Array([0.0, 1.0, 0.0, 0.0]), Array([0.0, 0.0, 1.0, 0.0]), Array([0.0, 0.0, 0.0, 1.0])] m = Matrix( MatrixN(range(9), shape=(3, 3)).trimmed(shape=(4, 4), value=10) ) print m.formated() #[[0.0, 1.0, 2.0, 10.0], # [3.0, 4.0, 5.0, 10.0], # [6.0, 7.0, 8.0, 10.0], # [10.0, 10.0, 10.0, 10.0]] print m.get() # ((0.0, 1.0, 2.0, 10.0), (3.0, 4.0, 5.0, 10.0), (6.0, 7.0, 8.0, 10.0), (10.0, 10.0, 10.0, 10.0)) print repr(m[0]) # [0.0, 1.0, 2.0, 10.0] m[0] = 10 print m.formated() #[[10.0, 10.0, 10.0, 10.0], # [3.0, 4.0, 5.0, 10.0], # [6.0, 7.0, 8.0, 10.0], # [10.0, 10.0, 10.0, 10.0]] print (10 in m) # True print list(m) # [Array([10.0, 10.0, 10.0, 10.0]), Array([3.0, 4.0, 5.0, 10.0]), Array([6.0, 7.0, 8.0, 10.0]), Array([10.0, 10.0, 10.0, 10.0])] print list(m.flat) # [10.0, 10.0, 10.0, 10.0, 3.0, 4.0, 5.0, 10.0, 6.0, 7.0, 8.0, 10.0, 10.0, 10.0, 10.0, 10.0] u = Vector.xAxis v = Vector.yAxis print Vector.xAxis print str(Vector.xAxis) print unicode(Vector.xAxis) print repr(Vector.xAxis) print "u = Vector.xAxis:" print repr(u) # trans matrix : t: 1, 2, 3, r: 45, 90, 30, s: 0.5, 1.0, 2.0 m = Matrix([0.0, 4.1633363423443383e-17, -0.5, 0.0, 0.25881904510252079, 0.96592582628906831, 1.3877787807814459e-16, 0.0, 1.9318516525781366, -0.51763809020504159, 0.0, 0.0, 1.0, 2.0, 3.0, 1.0]) print "m:" print round(m, 2).formated() #[[0.0, 0.0, -0.5, 0.0], # [0.26, 0.97, 0.0, 0.0], # [1.93, -0.52, 0.0, 0.0], # [1.0, 2.0, 3.0, 1.0]] x = Vector.xAxis y = Vector.yAxis z = Vector.zAxis u = Vector(1, 2, 3) print "u:" print repr(u) # Vector([1, 2, 3]) print "u*m" print repr(u*m) # Vector([6.31319304794, 0.378937381963, -0.5]) print "m*u" print repr(m*u) # Vector([-1.5, 2.19067069768, 0.896575472168]) p=Point(1, 10, 100, 1) print "p:" print repr(p) # Point([1.0, 10.0, 100.0, 1.0]) print "p*m" print repr(p*m) # Point([196.773355709, -40.1045507576, 2.5, 1.0]) print "m*p" print repr(m*p) # Point([-50.0, 9.91807730799, -3.24452924947, 322.0]) print "v = [1, 2, 3]*m" v = VectorN([1, 2, 3])*m print repr(v) # VectorN([6.31319304794, 0.378937381963, -0.5]) print "v = [1, 2, 3, 1]*m" v = VectorN([1, 2, 3, 1])*m print repr(v) # VectorN([7.31319304794, 2.37893738196, 2.5, 1.0]) # should fail print "VectorN([1, 2, 3, 4, 5])*m" try : v = VectorN([1, 2, 3, 4, 5])*m except : print "Will raise ValueError: vector of size 5 and matrix of shape (4, 4) are not conformable for a VectorN * MatrixN multiplication" # herited print "m = Matrix(range(1, 17))" m = Matrix(range(1, 17)) print m.formated() #[[1.0, 2.0, 3.0, 4.0], # [5.0, 6.0, 7.0, 8.0], # [9.0, 10.0, 11.0, 12.0], # [13.0, 14.0, 15.0, 16.0]] # element wise print "[1, 10, 100]*m" print repr([1, 10, 100]*m) # Matrix([[1.0, 20.0, 300.0, 0.0], [5.0, 60.0, 700.0, 0.0], [9.0, 100.0, 1100.0, 0.0], [13.0, 140.0, 1500.0, 0.0]]) print "M = MatrixN(range(20), shape=(4, 5))" M = MatrixN(range(1, 21), shape=(4, 5)) print M.formated() #[[1, 2, 3, 4, 5], # [6, 7, 8, 9, 10], # [11, 12, 13, 14, 15], # [16, 17, 18, 19, 20]] print "m*M" n = m*M print (n).formated() #[[110.0, 120.0, 130.0, 140.0, 150.0], # [246.0, 272.0, 298.0, 324.0, 350.0], # [382.0, 424.0, 466.0, 508.0, 550.0], # [518.0, 576.0, 634.0, 692.0, 750.0]] print util.clsname(n) # MatrixN print "m*2" n = m*2 print (n).formated() #[[2.0, 4.0, 6.0, 8.0], # [10.0, 12.0, 14.0, 16.0], # [18.0, 20.0, 22.0, 24.0], # [26.0, 28.0, 30.0, 32.0]] print util.clsname(n) # Matrix print "2*m" n = 2*m print (n).formated() #[[2.0, 4.0, 6.0, 8.0], # [10.0, 12.0, 14.0, 16.0], # [18.0, 20.0, 22.0, 24.0], # [26.0, 28.0, 30.0, 32.0]] print util.clsname(n) # Matrix print "m+2" n=m+2 print (n).formated() #[[3.0, 4.0, 5.0, 6.0], # [7.0, 8.0, 9.0, 10.0], # [11.0, 12.0, 13.0, 14.0], # [15.0, 16.0, 17.0, 18.0]] print util.clsname(n) # Matrix print "2+m" n=2+m print (n).formated() #[[3.0, 4.0, 5.0, 6.0], # [7.0, 8.0, 9.0, 10.0], # [11.0, 12.0, 13.0, 14.0], # [15.0, 16.0, 17.0, 18.0]] print util.clsname(n) # Matrix try : m.setToProduct(m, M) except : print """Will raise TypeError: cannot initialize a Matrix of shape (4, 4) from (Array([0, 1, 2, 3, 4]), Array([5, 6, 7, 8, 9]), Array([10, 11, 12, 13, 14]), Array([15, 16, 17, 18, 19])) of shape (4, 5), as it would truncate data or reduce the number of dimensions""" print m.isEquivalent(m*M) # False # trans matrix : t: 1, 2, 3, r: 45, 90, 30, s: 0.5, 1.0, 2.0 m = Matrix([0.0, 4.1633363423443383e-17, -0.5, 0.0, 0.25881904510252079, 0.96592582628906831, 1.3877787807814459e-16, 0.0, 1.9318516525781366, -0.51763809020504159, 0.0, 0.0, 1.0, 2.0, 3.0, 1.0]) print "m:" print round(m, 2).formated() #[[0.0, 0.0, -0.5, 0.0], # [0.26, 0.97, 0.0, 0.0], # [1.93, -0.52, 0.0, 0.0], # [1.0, 2.0, 3.0, 1.0]] print "m.transpose():" print round(m.transpose(),2).formated() #[[0.0, 0.26, 1.93, 1.0], # [0.0, 0.97, -0.52, 2.0], # [-0.5, 0.0, 0.0, 3.0], # [0.0, 0.0, 0.0, 1.0]] print "m.isSingular():" print m.isSingular() # False print "m.inverse():" print round(m.inverse(),2).formated() #[[0.0, 0.26, 0.48, 0.0], # [0.0, 0.97, -0.13, 0.0], # [-2.0, 0.0, 0.0, 0.0], # [6.0, -2.19, -0.22, 1.0]] print "m.adjoint():" print round(m.adjoint(),2).formated() #[[0.0, 0.26, 0.48, 0.0], # [0.0, 0.97, -0.13, 0.0], # [-2.0, 0.0, -0.0, 0.0], # [6.0, -2.19, -0.22, 1.0]] print "m.adjugate():" print round(m.adjugate(),2).formated() #[[0.0, 0.26, 0.48, 0.0], # [0.0, 0.97, -0.13, 0.0], # [-2.0, 0.0, -0.0, 0.0], # [6.0, -2.19, -0.22, 1.0]] print "m.homogenize():" print round(m.homogenize(),2).formated() #[[0.0, 0.0, -1.0, 0.0], # [0.26, 0.97, 0.0, 0.0], # [0.97, -0.26, -0.0, 0.0], # [1.0, 2.0, 3.0, 1.0]] print "m.det():" print m.det() # 1.0 print "m.det4x4():" print m.det4x4() # 1.0 print "m.det3x3():" print m.det3x3() # 1.0 print "m.weighted(0.5):" print round(m.weighted(0.5),2).formated() #[[0.53, 0.0, -0.53, 0.0], # [0.09, 0.99, 0.09, 0.0], # [1.05, -0.2, 1.05, 0.0], # [0.5, 1.0, 1.5, 1.0]] print "m.blend(Matrix.identity, 0.5):" print round(m.blend(Matrix.identity, 0.5),2).formated() #[[0.53, 0.0, -0.53, 0.0], # [0.09, 0.99, 0.09, 0.0], # [1.05, -0.2, 1.05, 0.0], # [0.5, 1.0, 1.5, 1.0]] print "end tests Matrix" def _testMTransformationMatrix() : q = Quaternion() print repr(q) # Quaternion([0.0, 0.0, 0.0, 1.0]) q = Quaternion(1, 2, 3, 0.5) print repr(q) # Quaternion([1.0, 2.0, 3.0, 0.5]) q = Quaternion(0.785, 0.785, 0.785, "xyz") print repr(q) # Quaternion([0.191357439088, 0.461717715523, 0.191357439088, 0.844737481223]) m = Matrix() m.rotate = q print repr(m) # Matrix([[0.500398163355, 0.499999841466, -0.706825181105, 0.0], [-0.146587362969, 0.853529322022, 0.499999841466, 0.0], [0.853295859083, -0.146587362969, 0.500398163355, 0.0], [0.0, 0.0, 0.0, 1.0]]) print "TransformationMatrix class", dir(TransformationMatrix) m = TransformationMatrix() print m.formated() #[[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]] print m[0, 0] # 1.0 print m[0:2, 0:3] # [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] print "TransformationMatrix instance:", dir(m) print TransformationMatrix.__readonly__ print TransformationMatrix.__slots__ print TransformationMatrix.shape print TransformationMatrix.ndim print TransformationMatrix.size print m.shape print m.ndim print m.size # should fail m.shape = (4, 4) m.shape = 2 print dir(Space) m = TransformationMatrix.identity # inherits from MatrixN --> Array print isinstance(m, MatrixN) # True print isinstance(m, Array) # True # as well as _api.TransformationMatrix and _api.Matrix print isinstance(m, _api.MTransformationMatrix) # True print isinstance(m, _api.MMatrix) # True # accepted directly by API methods n = _api.MMatrix() n = n.setToProduct(m, m) print repr(n) n = _api.MTransformationMatrix() n = n.assign(m) print repr(n) m = TransformationMatrix.identity m.rotation = Quaternion() print repr(m) print m.formated() n = TransformationMatrix.identity n.translation = Vector(1, 2, 3) print n.formated() print repr(n) o = m*n print repr(o) print o.formated() print "end tests TransformationMatrix" if __name__ == '__main__' : print Distance.getInternalUnit() # centimeters print Distance.getUIUnit() # centimeters Distance.setUIUnit('meters') print Distance.getUIUnit() # meters d = Distance(12) print d.unit # meters print d 1200.0 print repr(d) Distance(12.0, unit='meters') print d.asUnit() 12.0 print d.asInternalUnit() 1200.0 import doctest doctest.testmod(verbose=True) _testMVector() _testMPoint() _testMColor() _testMMatrix() _testMTransformationMatrix()