mrv.maya.nt.persistence
Covered: 163 lines
Missed: 6 lines
Skipped 93 lines
Percent: 96 %
  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"
  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')
 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
 44
if not hasattr(sys, "_maya_pyPickleData_trackingDict"):
 45
	sys._maya_pyPickleData_trackingDict = dict()
 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()
 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)
 76
		messageData = mAttr.create(p+"mmessage", p+"dmsg")
 77
		mAttr.setArray(True)
 80
		cAttr.addChild(dataID)
 81
		cAttr.addChild(typeInfo)
 82
		cAttr.addChild(typedData)
 83
		cAttr.addChild(messageData)
 86
	cAttr.setArray(True)
 90
	return api.MObject(aData)
 92
class StoragePluginNode(mpx.MPxNode):
 93
	""" Base Class defining the storage node data interfaces  """
 96
	kPluginNodeTypeName = "storageNode"
 97
	kPluginNodeId = api.MTypeId(0x0010D134)
 99
	aData = api.MObject()
101
	def __init__(self):
102
		return mpx.MPxNode.__init__(self)
104
	@staticmethod
105
	def creator():
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
119
	by the API Docs
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"""
128
	kPluginDataId = api.MTypeId(0x0010D135)
129
	kDataName = "PickleData"
131
	def __init__(self):
132
		mpx.MPxData.__init__(self)
133
		self.__data = dict()
134
		sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
136
	def __del__(self):
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)])
144
		try:
145
			super(PyPickleData, self).__del__()
146
		except AttributeError:
149
			pass
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
161
		if not asBinary:
162
			api.MStreamUtils.writeChar(ostream, '"', asBinary)
165
		if asBinary:
166
			for c in range(sout.tell() % 4):
167
				sout.write(chr(0))		# 0 bytes
172
		api.MStreamUtils.writeCharBuffer(ostream, binascii.b2a_base64(sout.getvalue()).strip(), asBinary)
174
		if not 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()
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)
203
			for shift in shiftlist:
204
				sio.write(chr((intval >> shift) & bitmask))
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))
221
		parsedIndex += 1
222
		api.MScriptUtil.setUint(lastParsedElement,parsedIndex)	# proceed the index
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
234
	@staticmethod
235
	def creator():
236
		return mpx.asMPxPtr(PyPickleData())
238
	def typeId(self):
239
		return self.kPluginDataId
241
	def name(self):
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)
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)