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

Source Code for Module animio.ui

  1  # -*- coding: utf-8 -*-
 
  2  """Module containing the user interface implementation of the AnimIO library""" 
  3  __docformat__ = "restructuredtext" 
  4  
 
  5  import animio.lib as lib 
  6  import mrv.maya.nt as nt 
  7  import mrv.maya.ui as ui 
  8  import mrv.maya as mrvmaya 
  9  from mrv.path import Path 
 10  from mrv.maya.ns import Namespace, RootNamespace 
 11  from mrv.maya.util import noneToList 
 12  
 
 13  import maya.cmds as cmds 
 14  import maya.OpenMayaAnim as apianim 
 15  
 
 16  from itertools import chain 
 17  import logging 
 18  log = logging.getLogger("animio.ui") 
 19  
 
 20  
 
21 -class FloatRangeField( ui.RowLayout ):
22 """Implements a queryable range of integers 23 :note: it uses text fields allowing them to be empty""" 24 25 #{ Signals 26 # none currently, but if this was a real element, it would surely allow changed 27 # events to happen 28 #} END signals 29
30 - def __new__(cls, *args, **kwargs):
31 """Assure we always have two columns with an appropriate size""" 32 # bail out early, otherwise we have to verify all our creation flags 33 if kwargs: 34 raise ValueError("Configure me afterwards please") 35 36 kwargs['nc'] = 2 37 kwargs['cw2'] = (40,40) 38 kwargs['adj'] = 2 39 40 return super(FloatRangeField, cls).__new__(cls, *args, **kwargs)
41 42
43 - def __init__(self, *args, **kwargs):
44 """Build our interface""" 45 46 ui.TextField(w=44) 47 ui.TextField(w=38) 48 49 # hide that we are a layout actually and restore the previous parent 50 self.setParentActive()
51 52 53 #{ Interface 54
55 - def get(self):
56 """:return: Tuple(floatStartRance, floatEndRange) 57 :raise ValueError: if one of the ranges is invalid""" 58 fs, fe = self.children() 59 return (float(fs.p_text), float(fe.p_text))
60
61 - def set(self, start, end):
62 """Set the range of this element 63 :param start: start of the range as float 64 :param end: end of the range as float)""" 65 fs, fe = self.children() 66 fs.p_text = "%g" % start 67 fe.p_text = "%g" % end
68
69 - def clear(self):
70 """Don't display any value, clear out the existing ones""" 71 for field in self.children(): 72 field.p_text = ""
73 # END for each field 74
75 - def setEnabled(self, state):
76 for field in reversed(self.children()): 77 field.p_enable = state 78 79 # refresh the UI basically, also good to have the focus where you want it 80 field.setFocus()
81 # END for each child 82 83 #} END interface 84 85
86 -class NodeSelector( ui.TextScrollList ):
87 """Element allowing the user to select nodes. 88 Either selected ones, or by namespace. The interface provides methods to retrieve 89 that information 90 91 :note: requires update once the scene changes - the parent is responsible for this""" 92 93 kSelectedNodes = "Selected Nodes" 94
95 - def __new__(cls, *args, **kwargs):
96 """Initialize the instance according to our needs 97 98 :param **kwargs: Additional configuration 99 100 * **show_selected_nodes** : If True, default True, the user may specify 101 to get the current node selection included in the managed set of nodes 102 """ 103 show_selected = kwargs.pop('show_selected_nodes', True) 104 if kwargs: 105 raise ValueError("Please do not specify any kwargs") 106 # END input handling 107 108 kwargs['allowMultiSelection'] = 1 109 inst = super(NodeSelector, cls).__new__(cls, *args, **kwargs) 110 111 inst._show_selected = show_selected 112 return inst
113 114 #{ Callbacks 115 116 #} END callbacks 117 118 #{ Interface 119
120 - def update(self):
121 """Call to force the element to update according to the contents of the 122 scene""" 123 curItems = noneToList(self.p_selectItem) 124 self.p_removeAll = 1 125 126 # add all items according to the scene and the configuration 127 if self._show_selected: 128 self.p_append = self.kSelectedNodes 129 130 for ns in RootNamespace.children(): 131 self.p_append = ns 132 # END for each namespace in scene 133 134 # reselect previous items 135 for sli in curItems: 136 try: 137 self.p_selectItem = sli 138 except RuntimeError: 139 pass
140 # END ignore exceptions 141 # END for each previously selected item 142
143 - def uses_selection(self):
144 """:return: True if the user wants to handle selected nodes""" 145 return self.kSelectedNodes in noneToList(self.p_selectItem)
146
147 - def set_uses_selection(self, state):
148 """Sets this element to return selected nodes when queried in 'iter_nodes' 149 if state is True 150 :note: only works if set_show_selected was called with a True value 151 :return: self""" 152 if not self._show_selected: 153 raise ValueError("This element does not allow to use 'Selected Nodes'") 154 155 if state: 156 self.p_selectItem = self.kSelectedNodes 157 else: 158 self.p_deselectItem = self.kSelectedNodes 159 # END 160 return self
161
162 - def set_show_selected(self, state):
163 """If state is True, we will allow the user to pick 'selected nodes' 164 :return: self""" 165 self._show_selected = state 166 self.update() 167 return self
168
169 - def show_seleted(self):
170 return self._show_selected
171
172 - def selected_namespaces(self):
173 """:return: list(Namespace, ...) list of Namespace objects which have 174 been selected""" 175 out = list() 176 for item_name in noneToList(self.p_selectItem): 177 if item_name == self.kSelectedNodes: 178 continue 179 # END skip sel node special item 180 181 ns = Namespace(item_name) 182 out.append(ns) 183 assert ns.exists(), "Selected namespace did not exist: %s " % ns 184 # END for each item 185 return out
186
187 - def select_namespaces(self, iter_ns):
188 """Select the given namespaces on our list if they exist. 189 :param iter_ns: iterable yielding namespace objects - they must be absolute 190 :return: self""" 191 for ns in iter_ns: 192 assert str(ns) != self.kSelectedNodes, "Cannot change our node-selection state here" 193 try: 194 self.p_selectItem = ns 195 except RuntimeError: 196 pass 197 # END ignore errors 198 # END for each namespace to selet 199 200 return self
201 202
203 - def iter_nodes(self, *args, **kwargs):
204 """ 205 :return: iterator yielding all selected nodes ( if set by the user ) 206 as well as all nodes in all selected namespaces 207 :param *args: passed to ``Namespace.iterNodes`` 208 :param **kwargs: passed to ``Namespace.iterNodes`` 209 :note: *args and **kwargs are passed to ``iterSelectionList`` as good 210 as applicable""" 211 iterators = list() 212 213 # HANDLE SELECTIONs 214 if self.uses_selection(): 215 # configure the selection list iterator as good as we can 216 iterkwargs = dict() 217 if args: 218 iterkwargs['filterType'] = args[0] 219 # END use type filter 220 iterkwargs['asNode'] = kwargs.get('asNode', True) 221 iterkwargs['handlePlugs'] = False 222 223 iterators.append(nt.activeSelectionList().mtoIter(**iterkwargs)) 224 # END handle selected nodes 225 226 # HANDLE NAMESPACES 227 for ns in self.selected_namespaces(): 228 iterators.append(ns.iterNodes(*args, **kwargs)) 229 # END for each namespace 230 231 return chain(*iterators)
232 233 #} END interface 234 235
236 -class ExportLayout( ui.FormLayout ):
237 """Layout encapsulating all export functionality""" 238 239 #{ Annotations 240 aHelp = "...need Help?" 241 aExport = "Export the current selection into a file of your choice" 242 243 #} END annotations 244
245 - def __init__(self, *args, **kwargs):
246 247 #{ members we care about 248 self.nodeselector = None 249 self.range = None 250 self.filetype = None 251 self.rangetype = None 252 #} END members 253 254 # CREATE UI 255 ############ 256 self.nodeselector = NodeSelector() 257 eBttn = ui.Button(label="Export...", ann=self.aExport) 258 eHB = ui.Button( label="?", ann=self.aHelp, w=22, h=22) 259 260 # RIGHT HAND SIDE 261 ################# 262 eClm = ui.ColumnLayout(adjustableColumn=True) 263 if eClm: 264 # TIME RANGE 265 ############ 266 # NOTE: for now we deactivate the range, as we do not yet support it 267 ui.Text(l="Timerange:", fn="boldLabelFont", al="left").p_manage = False 268 self.rangetype = ui.RadioCollection() 269 if self.rangetype: 270 ui.RadioButton(l="complete anim.", sl=1).p_manage = False 271 anim_mode_custom = ui.RadioButton(l="custom:") 272 anim_mode_custom.p_manage = False 273 # END radio collection 274 275 self.range = FloatRangeField() 276 self.range.p_manage = False 277 278 279 ui.Separator(h=40, style="none") 280 281 # FILE TYPE 282 ########### 283 ui.Text(l="Filetype", fn="boldLabelFont", align="left") 284 self.filetype = ui.RadioCollection() 285 if self.filetype: 286 ui.RadioButton(l="mayaAscii", sl=1) 287 ui.RadioButton(l="mayaBinary") 288 # END radio collection 289 290 ui.Separator(h=20, style="none") 291 # END column layout 292 self.setActive() 293 294 # SETUP FORM 295 ############ 296 t, b, l, r = self.kSides 297 self.setup( 298 attachForm=[ 299 (self.nodeselector, t, 0), 300 (self.nodeselector, l, 0), 301 (self.nodeselector, r, 95), 302 303 (eBttn, l, 0), 304 (eBttn, b, 0), 305 306 (eHB, b, 0), 307 (eHB, r, 2), 308 309 (eClm, r, 2)], 310 311 attachControl=[ 312 (self.nodeselector, b, 5, eBttn), 313 (eBttn, r, 0, eHB), 314 315 (eClm, l, 5, self.nodeselector), 316 (eClm, b, 5, eBttn)], 317 318 attachNone=[ 319 (eBttn, t), 320 321 (eHB, t), 322 (eHB, l), 323 324 (eClm, t)] ) 325 326 327 # SETUP CONNECTIONS 328 ################### 329 # connections we setup here as we don't need to keep the elements around 330 # for this simple secondary behaviour 331 anim_mode_custom.e_changeCommand = self._range_mode_changed 332 eBttn.e_released = self._on_export 333 eHB.e_released = self._show_help 334 335 # SET INITIAL STATE 336 ################### 337 self._range_mode_changed(anim_mode_custom) 338 self.update()
339 340 341 #{ Callbacks 342
343 - def _range_mode_changed(self, sender, *args):
344 """React if the animation mode changes, either enable our custom entry 345 field, or disable it""" 346 enable = sender.p_select 347 self.range.setEnabled(enable) 348 349 # set to playback range or clear the field 350 if enable: 351 self.range.set( apianim.MAnimControl.animationStartTime().value(), 352 apianim.MAnimControl.animationEndTime().value()) 353 else: 354 self.range.clear()
355 # END additional setop 356
357 - def _on_export(self, sender, *args):
358 """Perform the actual export after gathering UI data""" 359 # NOTE: Ignores timerange for now 360 if not self.nodeselector.uses_selection() and not self.nodeselector.selected_namespaces(): 361 raise ValueError("Please select what to export from the scroll list") 362 # END handle invalid input 363 364 # GET FILEPATH 365 # on linux, only one filter is possible - it would be good to have a 366 # capable file dialog coming from MRV ( in 2011 maybe just an adapter to 367 # fileDialog2 ) 368 file_path = cmds.fileDialog(mode=1,directoryMask="*.mb") 369 if not file_path: 370 return 371 # END bail out 372 373 extlist = ( ".ma", ".mb" ) 374 collection = [ p.basename() for p in ui.UI(self.filetype.p_collectionItemArray) ] 375 target_ext = extlist[collection.index(self.filetype.p_select)] 376 377 file_path = Path(file_path) 378 file_path = file_path.stripext() + target_ext 379 380 lib.AnimInOutLibrary.export(file_path, self.nodeselector.iter_nodes(asNode=False))
381
382 - def _show_help(self, sender, *args):
383 print "TODO: link to offline docs once they are written"
384 385 #} END callbacks 386 387 #{ Interface
388 - def update(self):
389 """Refresh our elements to represent the current scene state""" 390 self.nodeselector.update()
391 392 #} END interface 393 394
395 -class ImportLayout( ui.FormLayout ):
396 """Layout encapsulating all import functionality""" 397
398 - def __init__(self, **kwargs):
399 iRow1 = ui.RowLayout(cw=(1, 68), nc=2, adj=2) 400 401 # prefix 402 if iRow1: 403 iPrefCB = ui.CheckBox(w=68, l="add prefix:") 404 iPrefTF = ui.TextField() 405 iRow1.setParentActive() 406 407 # search and replace 408 iRow2 = ui.RowLayout(nc=4, cw=(1, 55), adj=4) 409 iRow2.p_cw = (2, 50) 410 iRow2.p_cw = (3, 42) 411 if iRow2: 412 iSearchCB = ui.CheckBox(w=55, l="search:") 413 iSearchTF = ui.TextField(w=50) 414 ui.Text(w=42, l=" replace:") 415 iReplaceTF = ui.TextField(w=45) 416 iRow2.setParentActive() 417 418 small = 20 419 iAddBttn = ui.Button(h=small, l="Add") 420 iTscl = ui.TextScrollList(name="AnimIOSearchReplace", w=190, numberOfRows=3, allowMultiSelection=True) 421 iDelBttn = ui.Button(h=small, l="Remove Selected") 422 423 # filter 424 iFilterL = ui.TextScrollList(name="AnimIOFilter", w=180, numberOfRows=5, allowMultiSelection=True) 425 iFilterCB = ui.CheckBox(w=100, l="filtered input:") 426 427 # buttons 428 iBttn = ui.Button(l="Import...") 429 iHB = ui.Button(label="?", ann="...need help?", w=22, h=22) 430 431 # options 432 iCol = ui.ColumnLayout(w=90, rs=2, adjustableColumn=True) 433 434 if iCol: 435 ui.Text(w=90, h=20, l="options:", fn="boldLabelFont", align="left") 436 iAniRepGr = ui.RadioButtonGrp(w=90, nrb=1, l1="replace", sl=1) 437 ui.RadioButtonGrp(nrb=1, scl=iAniRepGr, w=90, l1="insert") 438 ui.Text(w=90, h=20, l="import at...", fn="boldLabelFont", align="left") 439 iOriTimeGr = ui.RadioButtonGrp(w=90, nrb=1, l1="original time", sl=0) 440 ui.RadioButtonGrp(w=90, nrb=1, scl=iOriTimeGr, l1="current time", sl=1) 441 ui.Text(w=90, h=20, l="load timerange:", fn="boldLabelFont", align="left") 442 443 iTrCol = ui.RadioCollection() 444 iTrRadioG = list() 445 iTrRadioG.append(ui.RadioButton(w=90, cl=iTrCol, l="complete anim.", al="left")) 446 iTrRadioG.append(ui.RadioButton(w=90, cl=iTrCol, l="from file", al="left", sl=True)) 447 iTrRadioG.append(ui.RadioButton(w=90, cl=iTrCol, l="custom:", al="left")) 448 iRow3 = ui.RowLayout(nc=2, cw=(1, 45), adj=2) 449 iRow3.p_cw=(2, 40) 450 if iRow3: 451 iSTrTf = ui.TextField(w=44) 452 iETrTf = ui.TextField(w=38) 453 iRow3.setParentActive() 454 iTrRadioG.append(ui.RadioButton(w=90, cl=iTrCol, l="last pose", al="left")) 455 iTrRadioG.append(ui.RadioButton(w=90, cl=iTrCol, l="firs pose", al="left")) 456 # END column layout 457 self.setActive() 458 459 460 t, b, l, r = self.kSides 461 self.setup( 462 attachForm=[ 463 (iRow1, t, 0), 464 (iRow1, r, 2), 465 466 (iRow2, t, 25), 467 (iRow2, r, 2), 468 469 (iAddBttn, r, 2), 470 (iDelBttn, r, 2), 471 (iFilterCB, r, 2), 472 473 (iBttn, l, 0), 474 (iBttn, b, 0), 475 476 (iHB, b, 0), 477 (iHB, r, 2), 478 479 (iCol, l, 0), 480 (iFilterL, r, 2), 481 (iTscl, r, 2)], 482 483 attachControl=[ 484 (iRow1, b, 5, iRow2), 485 (iRow1, l, 10, iCol), 486 487 (iRow2, l, 10, iCol), 488 489 (iAddBttn, t, 5, iRow2), 490 (iAddBttn, l, 10, iCol), 491 492 (iDelBttn, b, 5, iFilterCB), 493 (iDelBttn, l, 10, iCol), 494 (iBttn, r, 0, iHB), 495 496 (iFilterCB, l, 10, iCol), 497 (iFilterL, t, 5, iFilterCB), 498 (iFilterL, b, 5, iBttn), 499 (iFilterL, l, 10, iCol), 500 501 (iTscl, t, 5, iAddBttn), 502 (iTscl, b, 5, iDelBttn), 503 (iTscl, l, 10, iCol)], 504 505 attachNone=[ 506 (iDelBttn, t), 507 (iFilterCB, b), 508 (iBttn, t), 509 (iHB, t), 510 (iHB, l)], 511 512 attachPosition=[ 513 (iFilterCB, t, 25, 50), 514 (iCol, b, -110, 50)])
515 516
517 -class AnimIOLayout( ui.TabLayout ):
518 """Represents a layout for exporting and importing animation""" 519
520 - def __init__(self, *args, **kwargs):
521 """Initialize ourselves with ui elements""" 522 # CREATE ELEMENTS 523 ################# 524 eFrame = ui.FrameLayout(label="Export Animation Of", labelAlign="top", borderStyle="etchedOut", mw=2, mh=5) 525 eFrame.p_mw = 2 526 527 if eFrame: 528 self.exportctrl = ExportLayout() 529 # END frame layout 530 self.setActive() 531 532 iFrame = ui.FrameLayout(label="Import Animation", labelAlign="top", li=57, borderStyle="etchedOut", mw=2, mh=5) 533 if iFrame: 534 self.importctrl = ImportLayout() 535 # END frame layout 536 self.setActive() 537 538 self.p_tabLabel = ((eFrame, "EXPORT"), (iFrame, "IMPORT")) 539 540 # SETUP CALLBACKS 541 ################# 542 mrvmaya.Scene.afterOpen = self.update 543 mrvmaya.Scene.afterNew = self.update
544 545 546 547 #{ Callbacks
548 - def uiDeleted(self):
549 """Deregister our scene callbacks""" 550 mrvmaya.Scene.afterOpen.remove(self.update) 551 mrvmaya.Scene.afterNew.remove(self.update)
552
553 - def update(self, *args):
554 """Update to represent the latest state of the scene""" 555 self.exportctrl.update()
556 #} END callbacks 557 558
559 -class AnimIO_UI( ui.Window ):
560
561 - def __init__(self, *args, **kwargs):
562 self.p_title = "mfAnimIO v0.8.py" 563 self.p_wh = (320, 362) 564 565 self.main = AnimIOLayout()
566