1
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')
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
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
39
40
41
42
43
44 if not hasattr(sys, "_maya_pyPickleData_trackingDict"):
45 sys._maya_pyPickleData_trackingDict = dict()
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")
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)
72 typedData = tAttr.create(p+"value", p+"dval", dataType)
73
74
75
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
86 cAttr.setArray(True)
87
88
89
90 return api.MObject(aData)
91
93 """ Base Class defining the storage node data interfaces """
94
95
96 kPluginNodeTypeName = "storageNode"
97 kPluginNodeId = api.MTypeId(0x0010D134)
98
99 aData = api.MObject()
100
103
104 @staticmethod
107
112
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
128 kPluginDataId = api.MTypeId(0x0010D135)
129 kDataName = "PickleData"
130
132 mpx.MPxData.__init__(self)
133 self.__data = dict()
134 sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
135
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
143
144 try:
145 super(PyPickleData, self).__del__()
146 except AttributeError:
147
148
149 pass
150
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
160
161 if not asBinary:
162 api.MStreamUtils.writeChar(ostream, '"', asBinary)
163
164
165 if asBinary:
166 for c in range(sout.tell() % 4):
167 sout.write(chr(0))
168
169
170
171
172 api.MStreamUtils.writeCharBuffer(ostream, binascii.b2a_base64(sout.getvalue()).strip(), asBinary)
173
174 if not asBinary:
175 api.MStreamUtils.writeChar(ostream, '"', asBinary)
176
178 """cPickle to cStringIO, write in 4 byte packs using ScriptUtil"""
179 self._writeToStream(out, True)
180
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
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
197 shiftlist = [0, 8, 16, 24]
198 for i in xrange(numBytesToRead / 4):
199 api.MStreamUtils.readInt(inStream, intptr, True)
200 intval = scriptutil.getInt(intptr)
201
202
203 for shift in shiftlist:
204 sio.write(chr((intval >> shift) & bitmask))
205
206
207
208 self.__data = cPickle.loads(binascii.a2b_base64(sio.getvalue()))
209 sys._maya_pyPickleData_trackingDict[mpx.asHashable(self)] = self.__data
210
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)
223
224
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
237
240
243
254
259
260
261