Package animio :: Module lib
[hide private]
[frames] | no frames]

Source Code for Module animio.lib

  1  # -*- coding: utf-8 -*- 
  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') 
20 21 22 23 -class AnimInOutLibrary( object ):
24 """contains default implementation for animation export and import""" 25 26 #{ Export/Import/Load 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 # END check for animation 49 50 tmphandle.to_file(destination_file) 51 return Path(destination_file) 52 finally: 53 rec.undo()
54 # END revert to previous state 55 56 57 #} END Export/Import/Load 58 59 #{ Query 60 61 #} END query 62
63 - def _create_plug_node( self ):
64 raise NotImplementedError("todo")
65
66 67 -class AnimationHandle( nt.Network ):
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
75 - def __new__( cls, *args ):
76 if not args: 77 return cls.create() 78 # END empty args create new node ( without calling __init__ ) 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
87 - def _is_handle( cls, ah ):
88 """:return: True if ah in a propper AnimationHandle""" 89 return hasattr(ah, cls._s_connection_info_attr)
90 91 #{ Iteration 92 @classmethod
93 - def iter_instances( cls, **kwargs ):
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 # END if it is our node 100 # END for each node 101
102 - def iter_animation( self, asNode=True ):
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 # END if asNode 112 # END iterator 113
114 - def iter_assignments( self, predicate=None, converter=None):
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 # get target strings as array 122 # mrv provides this: 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 # make iterator yielding source and target plug objects 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 # END check for nullPlug 137 138 mfndep.setObject(anim_node_msg_plug.node()) 139 anim_node_otp_plug = mfndep.findPlug('o') 140 141 # convert target names to actual plugs 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 # END handle converter 146 147 if predicate and not predicate(anim_node_otp_plug, tplug_name): 148 continue 149 # END filter 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 # END check if plug exists 158 159 plug_sel_list.getPlug(tindex, actual_plug) 160 yield (anim_node_otp_plug, actual_plug) 161 # END for each plugname to convert 162 163 # make sure it doesnt build up 164 plug_sel_list.clear()
165 # END for each anim node source plug 166 # END iterating 167 168 169 #} END iteration 170 171 #{ Edit 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 # add our custom attribute 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
187 - def clear( self ):
188 """Forget our managed animation completely""" 189 # clear array plug 190 for ip in self.affectedBy.minputs(): 191 ip.disconnectInput() 192 # END for each array item to disconnect 193 194 # clear connection data 195 dplug = self.findPlug(self._s_connection_info_attr) 196 dplug.setMObject(nt.StringArrayData.create(list()))
197 198 @undoable
199 - def set_animation( self, iter_nodes ):
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 # END for each pair to yield 217 # END iterator helper 218 219 iterator = iter_plugs() 220 nt.api.MPlug.mconnectMultiToMulti(iterator, force=False) 221 222 # add current connection info 223 # NOTE: We know that the anim-node is connected to something 224 # as this is the reason we retrieved it in the first place 225 # TODO: Deal with intermediate nodes 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 # END for each node 232 self.findPlug(self._s_connection_info_attr).setMObject(nt.StringArrayData.create(target_plug_strings)) 233 234 @undoable
235 - def apply_animation( self, converter=None ):
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 # do actual connection ( best case is 38k connections per second ) 244 iterator = self.iter_assignments(converter=converter) 245 nt.api.MPlug.mconnectMultiToMulti(iterator, force=True)
246 247 #} END edit 248 249 #{ Utilities 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 # get animCurves form plugs and copy pate 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 # END get new or existing animCurve 269 270 cmds.copyKey(s_animcrv, time=sTimeRange, option="curve" ) 271 cmds.pasteKey(t_animcrv, time=tTimeRange, option=option)
272 # END for each assignment 273 274 #} END Utilities 275 276 #{ File IO 277 278 @classmethod 279 @notundoable
280 - def from_file( cls, input_file ):
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 # build selectionlist for export 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
303 - def delete( self ):
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 # END handle referencing 312 313 #} END file io 314