1
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
22 """Implements a queryable range of integers
23 :note: it uses text fields allowing them to be empty"""
24
25
26
27
28
29
31 """Assure we always have two columns with an appropriate size"""
32
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
44 """Build our interface"""
45
46 ui.TextField(w=44)
47 ui.TextField(w=38)
48
49
50 self.setParentActive()
51
52
53
54
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
70 """Don't display any value, clear out the existing ones"""
71 for field in self.children():
72 field.p_text = ""
73
74
76 for field in reversed(self.children()):
77 field.p_enable = state
78
79
80 field.setFocus()
81
82
83
84
85
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
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
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
115
116
117
118
119
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
127 if self._show_selected:
128 self.p_append = self.kSelectedNodes
129
130 for ns in RootNamespace.children():
131 self.p_append = ns
132
133
134
135 for sli in curItems:
136 try:
137 self.p_selectItem = sli
138 except RuntimeError:
139 pass
140
141
142
144 """:return: True if the user wants to handle selected nodes"""
145 return self.kSelectedNodes in noneToList(self.p_selectItem)
146
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
160 return self
161
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
170 return self._show_selected
171
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
180
181 ns = Namespace(item_name)
182 out.append(ns)
183 assert ns.exists(), "Selected namespace did not exist: %s " % ns
184
185 return out
186
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
198
199
200 return self
201
202
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
214 if self.uses_selection():
215
216 iterkwargs = dict()
217 if args:
218 iterkwargs['filterType'] = args[0]
219
220 iterkwargs['asNode'] = kwargs.get('asNode', True)
221 iterkwargs['handlePlugs'] = False
222
223 iterators.append(nt.activeSelectionList().mtoIter(**iterkwargs))
224
225
226
227 for ns in self.selected_namespaces():
228 iterators.append(ns.iterNodes(*args, **kwargs))
229
230
231 return chain(*iterators)
232
233
234
235
237 """Layout encapsulating all export functionality"""
238
239
240 aHelp = "...need Help?"
241 aExport = "Export the current selection into a file of your choice"
242
243
244
246
247
248 self.nodeselector = None
249 self.range = None
250 self.filetype = None
251 self.rangetype = None
252
253
254
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
261
262 eClm = ui.ColumnLayout(adjustableColumn=True)
263 if eClm:
264
265
266
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
274
275 self.range = FloatRangeField()
276 self.range.p_manage = False
277
278
279 ui.Separator(h=40, style="none")
280
281
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
289
290 ui.Separator(h=20, style="none")
291
292 self.setActive()
293
294
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
328
329
330
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
336
337 self._range_mode_changed(anim_mode_custom)
338 self.update()
339
340
341
342
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
350 if enable:
351 self.range.set( apianim.MAnimControl.animationStartTime().value(),
352 apianim.MAnimControl.animationEndTime().value())
353 else:
354 self.range.clear()
355
356
358 """Perform the actual export after gathering UI data"""
359
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
363
364
365
366
367
368 file_path = cmds.fileDialog(mode=1,directoryMask="*.mb")
369 if not file_path:
370 return
371
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
383 print "TODO: link to offline docs once they are written"
384
385
386
387
389 """Refresh our elements to represent the current scene state"""
390 self.nodeselector.update()
391
392
393
394
396 """Layout encapsulating all import functionality"""
397
399 iRow1 = ui.RowLayout(cw=(1, 68), nc=2, adj=2)
400
401
402 if iRow1:
403 iPrefCB = ui.CheckBox(w=68, l="add prefix:")
404 iPrefTF = ui.TextField()
405 iRow1.setParentActive()
406
407
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
424 iFilterL = ui.TextScrollList(name="AnimIOFilter", w=180, numberOfRows=5, allowMultiSelection=True)
425 iFilterCB = ui.CheckBox(w=100, l="filtered input:")
426
427
428 iBttn = ui.Button(l="Import...")
429 iHB = ui.Button(label="?", ann="...need help?", w=22, h=22)
430
431
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
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
518 """Represents a layout for exporting and importing animation"""
519
521 """Initialize ourselves with ui elements"""
522
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
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
536 self.setActive()
537
538 self.p_tabLabel = ((eFrame, "EXPORT"), (iFrame, "IMPORT"))
539
540
541
542 mrvmaya.Scene.afterOpen = self.update
543 mrvmaya.Scene.afterNew = self.update
544
545
546
547
549 """Deregister our scene callbacks"""
550 mrvmaya.Scene.afterOpen.remove(self.update)
551 mrvmaya.Scene.afterNew.remove(self.update)
552
554 """Update to represent the latest state of the scene"""
555 self.exportctrl.update()
556
557
558
560
562 self.p_title = "mfAnimIO v0.8.py"
563 self.p_wh = (320, 362)
564
565 self.main = AnimIOLayout()
566