2
"""generic python style persitance plugin
3
This module contains a storage interface able to easily handle python-style
4
data within maya scenes.
6
__docformat__ = "restructuredtext"
14
log = logging.getLogger('mrv.maya.nt.persistence')
16
import maya.OpenMaya as api
17
import maya.OpenMayaMPx as mpx
19
persistence_enabled_envvar = "MRV_PERSISTENCE_ENABLED"
20
_should_initialize_plugin = int(os.environ.get(persistence_enabled_envvar, False))
23
__all__ = ('persistence_enabled_envvar', 'PyPickleData', 'createStorageAttribute')
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"
34
if _should_initialize_plugin and not cmds.pluginInfo(pluginpath, q=1, loaded=1):
35
cmds.loadPlugin(pluginpath)
36
return _should_initialize_plugin
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()
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.
52
def createStorageAttribute(dataType, name_prefix=''):
53
""" This method creates an Attribute in a configuration suitable to be used
54
with the ``StorageBase`` interface.
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()
68
aData = cAttr.create(p+"data", p+"dta") # connect to instance transforms
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)
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")
80
cAttr.addChild(dataID)
81
cAttr.addChild(typeInfo)
82
cAttr.addChild(typedData)
83
cAttr.addChild(messageData)
85
# END COMPOUND ATTRIBUTE
88
# copy attribute, just to be sure we don't loose the object when cAttr goes out
90
return api.MObject(aData)
92
class StoragePluginNode(mpx.MPxNode):
93
""" Base Class defining the storage node data interfaces """
95
# The ID used here has been assigned by the autodesk support and is globally unique !
96
kPluginNodeTypeName = "storageNode"
97
kPluginNodeId = api.MTypeId(0x0010D134)
102
return mpx.MPxNode.__init__(self)
106
return mpx.asMPxPtr(StoragePluginNode())
108
def initStoragePluginNodeAttrs():
109
"""Called to initialize the attributes of the storage node"""
110
StoragePluginNode.aData = createStorageAttribute(PyPickleData.kPluginDataId)
111
StoragePluginNode.addAttribute(StoragePluginNode.aData)
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.
118
To get the respective dict-references back, we use a tracking dict as proposed
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"""
127
# The ID used here has been assigned by the autodesk support and is globally unique !
128
kPluginDataId = api.MTypeId(0x0010D135)
129
kDataName = "PickleData"
132
mpx.MPxData.__init__(self)
134
sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
137
"""Remove ourselves from the dictionary to prevent flooding
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
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
151
def _writeToStream(self, ostream, asBinary):
152
"""Write our data binary or ascii respectively"""
153
sout = cStringIO.StringIO()
155
cPickle.dump(self.__data, sout, protocol=2)
156
except cPickle.PicklingError, e:
159
# END pickle error handling
162
api.MStreamUtils.writeChar(ostream, '"', asBinary)
164
# assure number of bytes is a multiple of 4
166
for c in range(sout.tell() % 4):
167
sout.write(chr(0)) # 0 bytes
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)
175
api.MStreamUtils.writeChar(ostream, '"', asBinary)
177
def writeBinary(self, out):
178
"""cPickle to cStringIO, write in 4 byte packs using ScriptUtil"""
179
self._writeToStream(out, True)
181
def readBinary(self, inStream, numBytesToRead):
182
"""Read in 4 byte packs to cStringIO, unpickle from there
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()
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)
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)
202
# convert to chars - endianess should be taken care of by python
203
for shift in shiftlist:
204
sio.write(chr((intval >> shift) & bitmask))
206
# END for all 4 bytes to read
208
self.__data = cPickle.loads(binascii.a2b_base64(sio.getvalue()))
209
sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
211
def writeASCII(self, out):
212
"""cPickle to cStringIO, encode with base64 encoding"""
213
self._writeToStream(out, False)
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))
222
api.MScriptUtil.setUint(lastParsedElement,parsedIndex) # proceed the index
224
# update tracking dict
225
sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
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
236
return mpx.asMPxPtr(PyPickleData())
239
return self.kPluginDataId
242
return self.kDataName
245
def initializePlugin(mobject):
246
import mrv.maya as mrv
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)
252
# register plugin data in the respective class
253
mrv.registerPluginDataTrackingDict(PyPickleData.kPluginDataId, sys._maya_pyPickleData_trackingDict)
255
def uninitializePlugin(mobject):
256
mplugin = mpx.MFnPlugin(mobject)
257
mplugin.deregisterData(PyPickleData.kPluginDataId)
258
mplugin.deregisterNode(StoragePluginNode.kPluginNodeId)