""" Copyright (c) 2012, Justin Israel (justinisrael@gmail.com) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of cmiVFX.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. outliner.py Chapter06 - Outliner Widget Replicating a basic working version of the Outliner """ from PyQt4 import QtCore, QtGui import sip import maya.cmds as cmds import maya.OpenMaya as om import maya.OpenMayaUI as mui from Chapter05.mqtutil import getMainWindow class Outliner(QtGui.QDialog): """ Outliner(QtGui.QDialog) Custom dialog widget that emulates a few basic features of the Maya Outliner """ def __init__(self, *args, **kwargs): super(Outliner, self).__init__(*args, **kwargs) self.resize(200,500) self.setObjectName("CustomOutliner") self.layout = QtGui.QVBoxLayout(self) self.layout.setMargin(2) self.model = QtGui.QStandardItemModel() self.model.setItemPrototype(DagTreeItem()) view = QtGui.QTreeView() view.setModel(self.model) view.header().setVisible(False) view.setEditTriggers(view.NoEditTriggers) view.setSelectionMode(view.ExtendedSelection) self.view = view self.layout.addWidget(self.view) QtCore.QTimer.singleShot(1, self.initDisplay) # # Connections # self.view.expanded.connect(self.nodeExpanded) self.view.selectionModel().selectionChanged.connect(self.selectionChanged) def initDisplay(self): """ Initialize the model with the root world items """ self.model.clear() excludes = set([ '|groundPlane_transform', '|Manipulator1', '|UniversalManip', '|CubeCompass', ]) roots = self.scanDag(mindepth=1, maxdepth=2, exclude=excludes) if roots: self.model.appendColumn(roots) def nodeExpanded(self, idx): """ nodeExpanded(QModelIndex idx) Slot to handle an item in the list being expanded. Populates the children of this items immediate children. """ item = self.model.itemFromIndex(idx) if item.hasChildren(): for row in xrange(item.rowCount()): child = item.child(row) child.removeRows(0, child.rowCount()) grandChildren = self.scanDag(child) if grandChildren: child.appendRows(grandChildren) def selectionChanged(self): """ selectionChanged() Slot called when the selection of the view has changed. Selects the corresponding nodes in the Maya scene that match the selected view items. """ nodes = [self.model.itemFromIndex(i).fullname for i in self.view.selectedIndexes()] if nodes: cmds.select(nodes, replace=True) else: cmds.select(clear=True) @staticmethod def scanDag(root=None, mindepth=1, maxdepth=1, exclude=None): """ scanDag(root=None, mindepth=1, maxdepth=1, exclude=None) -> list root - either an MDagPath or DagTreeItem to start from mindepth - starting depth of items to return (default 1; immediate children) maxdepth - ending depth of items to return (default 1; immediate children) exclude - a sequence of strings representing node paths that should be skipped Walks the DAG tree from a starting root, through a given depth range. Returns a list of the top level children of the root as DagTreeItem's. Decendants of these items already have been added as DagTreeItem children. mindepth or maxdepth may be set to -1, in which case those limits will be ignored altogether. """ # Allow either a DagTreeItem or an MDagPath if isinstance(root, DagTreeItem): root = root.dagObj dagIt = om.MItDag() root = root or dagIt.root() exclude = exclude or set() dagIt.reset(root, om.MItDag.kDepthFirst) # These will be our final top-most nodes from the search nodes = [] # This will map node string paths to the items to help us # easily look up a parent at any point in the search. itemMap = {} while not dagIt.isDone(): depth = dagIt.depth() # if the iterator has gone past our target # depth, prune out the tree from here on down, # so it is not used in future loops if (maxdepth > -1) and (depth > maxdepth): dagIt.prune() # this would allow us to skip past an amount of # levels from the root, if mindepth > 1 elif depth >= mindepth: dagPath = om.MDagPath() dagIt.getPath(dagPath) path = dagPath.fullPathName() if path and path not in exclude: item = DagTreeItem(dagPath) # save this item in our mapping itemMap[item.fullname] = item # If this item has a parent, add it to that # parent DagTreeItem. # Otherwise, just add it to our top level list parent = itemMap.get(item.parentname) if parent: parent.appendRow(item) else: nodes.append(item) # prune out items that were in our excludes list else: dagIt.prune() dagIt.next() return nodes @classmethod def showOutliner(cls): """ showOutliner() -> (str dockLayout, Outliner widget) Creates a Outliner widget inside of a Maya dockControl. Returns the dockControl path, and the Outliner widget. """ win = cls(parent=getMainWindow()) size = win.size() name = mui.MQtUtil.fullName(long(sip.unwrapinstance(win))) dock = cmds.dockControl( allowedArea='all', area='left', floating=False, content=name, width=size.width(), height=size.height(), label='Custom Outliner') return dock, win class DagTreeItem(QtGui.QStandardItem): """ DagTreeItem(QtGui.QStandardItem) QStandardItem subclass that represents a Dag node """ def __init__(self, dagObj=None): super(DagTreeItem, self).__init__() self.dagObj = dagObj self.setText(self.name) def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.name) @property def fullname(self): if not self.dagObj: return '' return self.dagObj.fullPathName() @property def name(self): return self.fullname.rsplit('|', 1)[-1] @property def parentname(self): return self.fullname.rsplit('|', 1)[0]