Package mrv :: Package maya :: Package nt :: Module persistence
[hide private]
[frames] | no frames]

Source Code for Module mrv.maya.nt.persistence

  1  # -*- coding: utf-8 -*- 
  2  """generic python style persitance plugin 
  3  This module contains a storage interface able to easily handle python-style 
  4  data within maya scenes.  
  5  """ 
  6  __docformat__ = "restructuredtext" 
  7   
  8  import os 
  9  import sys 
 10  import cPickle 
 11  import cStringIO 
 12  import binascii 
 13  import logging 
 14  log = logging.getLogger('mrv.maya.nt.persistence') 
 15   
 16  import maya.OpenMaya as api 
 17  import maya.OpenMayaMPx as mpx 
 18   
 19  persistence_enabled_envvar = "MRV_PERSISTENCE_ENABLED" 
 20  _should_initialize_plugin = int(os.environ.get(persistence_enabled_envvar, False)) 
 21   
 22   
 23  __all__ = ('persistence_enabled_envvar', 'PyPickleData', 'createStorageAttribute') 
24 25 #{ Initialization 26 27 -def __initialize(nodes_module):
28 """Assure our plugin is loaded - called during module intialization. 29 Its a tough time to run, it feels more like bootstrapping as we initialize 30 ourselves although the system is not quite there yet.""" 31 import maya.cmds as cmds # late import 32 pluginpath = os.path.splitext(__file__)[0] + ".py" 33 34 if _should_initialize_plugin and not cmds.pluginInfo(pluginpath, q=1, loaded=1): 35 cmds.loadPlugin(pluginpath) 36 return _should_initialize_plugin
37 38 #} END initialization 39 40 #{ Storage Plugin 41 42 # GLOBAL PERSITENCE TRACKING DICT 43 # assure we only have it once 44 if not hasattr(sys, "_maya_pyPickleData_trackingDict"): 45 sys._maya_pyPickleData_trackingDict = dict()
46 47 48 # NOTE: We do not prevent the code to be executed if we are not to load as, 49 # at this point, openMayaAnim has been initialized already which in fact 50 # loads OpenMayaMPx that we would try to delay. 51 52 -def createStorageAttribute(dataType, name_prefix=''):
53 """ This method creates an Attribute in a configuration suitable to be used 54 with the ``StorageBase`` interface. 55 56 :note: this allows your own plugin node to receive storage compatibility 57 :param dataType: the type of the typed attribute - either MTypeID or MFnData enumeration 58 An MTypeID must point to a valid and already registered plugin data. 59 In order for the ``StorageBase`` interface to work, it must by ``PyPickleData.kPluginDataId``. 60 :param name_prefix: string to be used as prefix for all short and long attribute names. 61 Useful if you want to have more than one storage attributes. 62 :return: attribute api object of its master compound attribute""" 63 tAttr = api.MFnTypedAttribute() 64 mAttr = api.MFnMessageAttribute() 65 cAttr = api.MFnCompoundAttribute() 66 nAttr = api.MFnNumericAttribute() 67 p = name_prefix 68 aData = cAttr.create(p+"data", p+"dta") # connect to instance transforms 69 if True: 70 dataID = tAttr.create(p+"dataId", p+"id", api.MFnData.kString) 71 typeInfo = nAttr.create(p+"dataType", p+"type", api.MFnNumericData.kInt) # can be used for additional type info 72 typedData = tAttr.create(p+"value", p+"dval", dataType) 73 74 # even though its a child attribute, we cannot use 'message' as it is already 75 # taken on the DependNode 76 messageData = mAttr.create(p+"mmessage", p+"dmsg") 77 mAttr.setArray(True) 78 79 80 cAttr.addChild(dataID) 81 cAttr.addChild(typeInfo) 82 cAttr.addChild(typedData) 83 cAttr.addChild(messageData) 84 85 # END COMPOUND ATTRIBUTE 86 cAttr.setArray(True) 87 88 # copy attribute, just to be sure we don't loose the object when cAttr goes out 89 # of scope 90 return api.MObject(aData)
91
92 -class StoragePluginNode(mpx.MPxNode):
93 """ Base Class defining the storage node data interfaces """ 94 95 # The ID used here has been assigned by the autodesk support and is globally unique ! 96 kPluginNodeTypeName = "storageNode" 97 kPluginNodeId = api.MTypeId(0x0010D134) 98 99 aData = api.MObject() 100
101 - def __init__(self):
102 return mpx.MPxNode.__init__(self)
103 104 @staticmethod
105 - def creator():
106 return mpx.asMPxPtr(StoragePluginNode())
107
108 -def initStoragePluginNodeAttrs():
109 """Called to initialize the attributes of the storage node""" 110 StoragePluginNode.aData = createStorageAttribute(PyPickleData.kPluginDataId) 111 StoragePluginNode.addAttribute(StoragePluginNode.aData)
112
113 -class PyPickleData(mpx.MPxData):
114 """Allows to access a pickled data object natively within a maya file. 115 In ascii mode, the pickle will be encoded into string data, in binary mode 116 the cPickle will be taken in its original value. 117 118 To get the respective dict-references back, we use a tracking dict as proposed 119 by the API Docs 120 121 :note: This datatype is copies the data by reference which is why maya always calls 122 the copy constructor, even if you retrieve a const data reference, where this would not be 123 required actually. This is fine for most uses 124 :note: as the datatype is reference based, undo is currently not supported (or does not 125 work as it is expected to do""" 126 127 # The ID used here has been assigned by the autodesk support and is globally unique ! 128 kPluginDataId = api.MTypeId(0x0010D135) 129 kDataName = "PickleData" 130
131 - def __init__(self):
132 mpx.MPxData.__init__(self) 133 self.__data = dict() 134 sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
135
136 - def __del__(self):
137 """Remove ourselves from the dictionary to prevent flooding 138 139 :note: we can be called even if maya is already unloaded or shutting down""" 140 if mpx.asHashable is not None: 141 del(sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)]) 142 # call super just to be on the safe side in future, currently it appears 143 # not to be required 144 try: 145 super(PyPickleData, self).__del__() 146 except AttributeError: 147 # Maya 2008 and following do not require this anymore as they 148 # do not have a del method implemented apparently 149 pass
150
151 - def _writeToStream(self, ostream, asBinary):
152 """Write our data binary or ascii respectively""" 153 sout = cStringIO.StringIO() 154 try: 155 cPickle.dump(self.__data, sout, protocol=2) 156 except cPickle.PicklingError, e: 157 log.error(str(e)) 158 return 159 # END pickle error handling 160 161 if not asBinary: 162 api.MStreamUtils.writeChar(ostream, '"', asBinary) 163 164 # assure number of bytes is a multiple of 4 165 if asBinary: 166 for c in range(sout.tell() % 4): 167 sout.write(chr(0)) # 0 bytes 168 169 # NOTE: even binaries will be encoded as this circumvents the 0 byte which terminates the 170 # char byte stream ... can't help it but writing individual bytes 171 # TODO: improve this if it turns out to be too slow 172 api.MStreamUtils.writeCharBuffer(ostream, binascii.b2a_base64(sout.getvalue()).strip(), asBinary) 173 174 if not asBinary: 175 api.MStreamUtils.writeChar(ostream, '"', asBinary)
176
177 - def writeBinary(self, out):
178 """cPickle to cStringIO, write in 4 byte packs using ScriptUtil""" 179 self._writeToStream(out, True)
180
181 - def readBinary(self, inStream, numBytesToRead):
182 """Read in 4 byte packs to cStringIO, unpickle from there 183 184 :note: this method is more complicated than it needs be since asCharPtr does not work ! 185 It returns a string of a single char ... which is not the same :) ! 186 :note: YES, this is a CUMBERSOME way to deal with bytes ... terrible, thanks maya :), thanks python""" 187 sio = cStringIO.StringIO() 188 scriptutil = api.MScriptUtil() 189 scriptutil.createFromInt(0) 190 intptr = scriptutil.asIntPtr() 191 192 # require multiple of 4 ! 193 if numBytesToRead % 4 != 0: 194 raise AssertionError("Require multiple of for for number of bytes to be read, but is %i" % numBytesToRead) 195 196 bitmask = 255 # mask the lower 8 bit 197 shiftlist = [0, 8, 16, 24] # used to shift bits by respective values 198 for i in xrange(numBytesToRead / 4): 199 api.MStreamUtils.readInt(inStream, intptr, True) 200 intval = scriptutil.getInt(intptr) 201 202 # convert to chars - endianess should be taken care of by python 203 for shift in shiftlist: 204 sio.write(chr((intval >> shift) & bitmask)) 205 # END for each byte 206 # END for all 4 bytes to read 207 208 self.__data = cPickle.loads(binascii.a2b_base64(sio.getvalue())) 209 sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
210
211 - def writeASCII(self, out):
212 """cPickle to cStringIO, encode with base64 encoding""" 213 self._writeToStream(out, False)
214
215 - def readASCII(self, args, lastParsedElement):
216 """Read base64 element and decode to cStringIO, then unpickle""" 217 parsedIndex = api.MScriptUtil.getUint(lastParsedElement) 218 base64encodedstring = args.asString(parsedIndex) 219 self.__data = cPickle.loads(binascii.a2b_base64(base64encodedstring)) 220 221 parsedIndex += 1 222 api.MScriptUtil.setUint(lastParsedElement,parsedIndex) # proceed the index 223 224 # update tracking dict 225 sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
226
227 - def copy(self, other):
228 """Copy other into self - allows copy pointers as maya copies the data each 229 time you retrieve it""" 230 otherdata = sys._maya_pyPickleData_trackingDict[mpx.asHashable(other)] 231 self.__data = otherdata 232 sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
233 234 @staticmethod
235 - def creator():
236 return mpx.asMPxPtr(PyPickleData())
237
238 - def typeId(self):
239 return self.kPluginDataId
240
241 - def name(self):
242 return self.kDataName
243
244 245 -def initializePlugin(mobject):
246 import mrv.maya as mrv 247 248 mplugin = mpx.MFnPlugin(mobject) 249 mplugin.registerData(PyPickleData.kDataName, PyPickleData.kPluginDataId, PyPickleData.creator) 250 mplugin.registerNode( StoragePluginNode.kPluginNodeTypeName, StoragePluginNode.kPluginNodeId, StoragePluginNode.creator, initStoragePluginNodeAttrs, mpx.MPxNode.kDependNode) 251 252 # register plugin data in the respective class 253 mrv.registerPluginDataTrackingDict(PyPickleData.kPluginDataId, sys._maya_pyPickleData_trackingDict)
254
255 -def uninitializePlugin(mobject):
256 mplugin = mpx.MFnPlugin(mobject) 257 mplugin.deregisterData(PyPickleData.kPluginDataId) 258 mplugin.deregisterNode(StoragePluginNode.kPluginNodeId)
259 260 #} END plugin 261