1
2 """This module contains classes and utilities affiliated with the import and export
3 of animation."""
4 __docformat__ = "restructuredtext"
5
6 import mrv.maya.nt as nt
7 from mrv.maya.ns import Namespace
8 from mrv.maya.ref import FileReference
9 from mrv.maya.scene import Scene
10 from mrv.maya.undo import UndoRecorder
11 from mrv.path import Path
12
13 import maya.OpenMayaAnim as apianim
14 import maya.cmds as cmds
15
16 import logging
17 log = logging.getLogger("animio.lib")
18
19 __all__ = ('AnimInOutLibrary', 'AnimationHandle')
24 """contains default implementation for animation export and import"""
25
26
27 @classmethod
28 @notundoable
29 - def export(cls, destination_file, iter_nodes, **kwargs):
30 """Export animation retrieved from the given node iterator to the destination_file.
31
32 :param destination_file: file to which to export the animation to
33 :param iter_nodes: iterator yielding nodes in a format compatible to ``AnimationHandle.set_animation``
34 :param **kwargs: passed to ``Scene.export``
35 :raise ValueError: if the passed in nodes have no animation
36 :return: destination_file as Path"""
37 rec = UndoRecorder()
38 rec.startRecording()
39 tmphandle = AnimationHandle()
40 tmphandle.set_animation(iter_nodes)
41 rec.stopRecording()
42
43 try:
44 try:
45 tmphandle.iter_animation().next()
46 except StopIteration:
47 raise ValueError("Given nodes did not have any animation")
48
49
50 tmphandle.to_file(destination_file)
51 return Path(destination_file)
52 finally:
53 rec.undo()
54
55
56
57
58
59
60
61
62
64 raise NotImplementedError("todo")
65
68 __mrv_virtual_subtype__ = True
69
70 _l_connection_info_attr = 'connectionInfo'
71 _s_connection_info_attr = 'cifo'
72 _k_separator = ','
73 _networktype = nt.api.MFn.kAffect
74
76 if not args:
77 return cls.create()
78
79
80 self=super(AnimationHandle, cls).__new__(cls, *args)
81 if not cls._is_handle(self):
82 raise TypeError('%r is not a valid %s' % (self, cls))
83
84 return self
85
86 @classmethod
90
91
92 @classmethod
94 """:return: iterator yielding AnimationHandle instances of scene"""
95 it=nt.it.iterDgNodes( cls._networktype, asNode=1, **kwargs )
96 for node in it:
97 if cls._is_handle(node):
98 yield cls(node.object())
99
100
101
103 """:return: iterator yielding managed animation curves as wrapped Node or MObject
104 :param asNode: if true, iterator yields Node instances else MObjects"""
105 for anim_node_dest_plug in self.affectedBy:
106 miplug=anim_node_dest_plug.minput()
107 if asNode:
108 yield miplug.mwrappedNode()
109 else:
110 yield miplug.node()
111
112
113
115 """:return: iterator yielding source-target assignments as plugs in a tuple(source_plug, target_plug)
116 :param converter: if not None, the function returns the desired target plug name to use
117 instead of the given plug name. Its called as follows: (string) convert(source_plug, target_plugname).
118 :param predicate: if not None, after the converter function has been applied,
119 (bool) predicate(source_plug, target_plugname) returns True for each plug to be yielded
120 :note: for now, if target_plug does not exist we just print a message and continue"""
121
122
123 target_plug_names = self.findPlug(self._s_connection_info_attr).masData().array()
124
125 assert len(target_plug_names) == len(self.affectedBy), "Number of animation nodes out of sync with their stored targets"
126
127
128 plug_sel_list = nt.api.MSelectionList()
129 mfndep = nt.api.MFnDependencyNode()
130 for index, anim_node_dest_plug in enumerate(self.affectedBy):
131 target_plug_name_list = target_plug_names[index].split(self._k_separator)
132 anim_node_msg_plug=anim_node_dest_plug.minput()
133 if anim_node_msg_plug.isNull():
134 log.warn("no animation curve found on %s" % anim_node_dest_plug.mfullyQualifiedName())
135 continue
136
137
138 mfndep.setObject(anim_node_msg_plug.node())
139 anim_node_otp_plug = mfndep.findPlug('o')
140
141
142 for tindex, tplug_name in enumerate(target_plug_name_list):
143 if converter:
144 tplug_name = converter(anim_node_otp_plug, tplug_name)
145
146
147 if predicate and not predicate(anim_node_otp_plug, tplug_name):
148 continue
149
150
151 actual_plug = nt.api.MPlug()
152 try:
153 plug_sel_list.add(tplug_name)
154 except:
155 log.warn("target plug named %s does not exist" % tplug_name)
156 continue
157
158
159 plug_sel_list.getPlug(tindex, actual_plug)
160 yield (anim_node_otp_plug, actual_plug)
161
162
163
164 plug_sel_list.clear()
165
166
167
168
169
170
171
172
173 @classmethod
174 @undoable
175 - def create( cls, name="animationHandle", **kwargs ):
176 """:return: New instance of our type providing the ``AnimationHandle`` interface
177 :param kwargs: Passed to ``createNode`` method of mrv"""
178 mynode = nt.createNode(name, "network", **kwargs)
179
180
181 attr = nt.TypedAttribute.create(cls._l_connection_info_attr, cls._s_connection_info_attr,
182 nt.api.MFnData.kStringArray, nt.StringArrayData.create(list()))
183 mynode.addAttribute(attr)
184 return cls(mynode.object())
185
186 @undoable
188 """Forget our managed animation completely"""
189
190 for ip in self.affectedBy.minputs():
191 ip.disconnectInput()
192
193
194
195 dplug = self.findPlug(self._s_connection_info_attr)
196 dplug.setMObject(nt.StringArrayData.create(list()))
197
198 @undoable
200 """Set this handle to manage the animation of the given nodes.
201 The previous animation information will be removed.
202
203 :param iter_nodes: MSelectionList or iterable of Nodes or api objects pointing
204 to nodes connected to animation.
205 :note: Will not raise if the nodes do not have any animation
206 :note: Heavily optimized for speed, hence we work directly with the
207 apiObjects, skipping the mrv layer as we are in a tight loop here"""
208 self.clear()
209 anim_nodes = nt.AnimCurve.findAnimation(iter_nodes, asNode=False)
210 mfndep = nt.api.MFnDependencyNode()
211 def iter_plugs():
212 affected_by_plug = self.affectedBy
213 for pindex, apinode in enumerate(anim_nodes):
214 mfndep.setObject(apinode)
215 yield (mfndep.findPlug('msg'), affected_by_plug.elementByLogicalIndex(pindex))
216
217
218
219 iterator = iter_plugs()
220 nt.api.MPlug.mconnectMultiToMulti(iterator, force=False)
221
222
223
224
225
226 target_plug_strings = list()
227 for apinode in anim_nodes:
228 mfndep.setObject(apinode)
229 outputs = mfndep.findPlug('o').moutputs()
230 target_plug_strings.append(self._k_separator.join(p.mfullyQualifiedName() for p in outputs))
231
232 self.findPlug(self._s_connection_info_attr).setMObject(nt.StringArrayData.create(target_plug_strings))
233
234 @undoable
236 """Apply the stored animation by (re)connecting the animation nodes to their
237 respective target plugs
238 :param: converter see ``iter_assignments``
239 This allows you to perform any modifications to the target before it will be
240 connected.
241 :note: Will break existing destination connections"""
242
243
244 iterator = self.iter_assignments(converter=converter)
245 nt.api.MPlug.mconnectMultiToMulti(iterator, force=True)
246
247
248
249
250 @undoable
251 - def paste_animation( self, sTimeRange=tuple(), tTimeRange=tuple(), option="fitInsert", predicate=None, converter=None ):
252 """paste the stored animation to their respective target animation curves, if target does not exist it will be created
253 :param sTimeRange: tuple of timerange passed to copyKey
254 :param tTimeRange: tuple of timerange passed to pasteKey
255 :param option: option on how to paste forwarded to pasteKey (useful: "fitInsert", "fitReplace", "scaleInsert", "scaleReplace")
256 :param predicate and converter: passed to ``iter_assignments``, see documentation there
257 :todo: handle if range is out of curve (error:nothing to paste from) - should paste the pose in this range"""
258 iter_plugs=self.iter_assignments(predicate=predicate, converter=converter)
259
260
261 for s_plug, t_plug in iter_plugs:
262 s_animcrv=s_plug.mwn()
263
264 if apianim.MAnimUtil.isAnimated(t_plug):
265 t_animcrv=t_plug.minput().mwn()
266 else:
267 t_animcrv=nt.Node(apianim.MFnAnimCurve().create(t_plug))
268
269
270 cmds.copyKey(s_animcrv, time=sTimeRange, option="curve" )
271 cmds.pasteKey(t_animcrv, time=tTimeRange, option=option)
272
273
274
275
276
277
278 @classmethod
279 @notundoable
281 """references imput_file into scene by using an unique namespace, returning
282 FileReference as well as an iterator yielding AmimationHandles of input_file
283
284 :return: tuple(FileReference, iterator of AnimationHandles)
285 :param input_file: valid path to a maya file"""
286 ahref=FileReference.create(input_file, loadReferenceDepth="topOnly")
287 refns=ahref.namespace()
288 return (ahref, cls.iter_instances(predicate = lambda x: x.namespace() == refns))
289
290 @notundoable
291 - def to_file( self, output_file, **kwargs ):
292 """export the AnimationHandle and all managed nodes to the given file
293
294 :return: path to exported file
295 :param output_file: Path object or path string to export file.
296 Parent directories will be created as needed
297 :param kwargs: passed to the ``Scene.export`` method"""
298
299 exp_slist = nt.toSelectionList(self.iter_animation(asNode=0))
300 exp_slist.add(self.object())
301 return Scene.export(output_file, exp_slist, **kwargs )
302
304 """AnimationHandle will disapear without a trace, no matter if it was created in
305 the current file or if it came from a referenced file"""
306
307 if self.isReferenced():
308 FileReference(self.referenceFile()).remove()
309 else:
310 super(AnimationHandle, self).delete()
311
312
313
314