From 3dc4c5c47100dab7bc5bd5e0f2cd054202a70551 Mon Sep 17 00:00:00 2001 From: shriramiyer Date: Mon, 29 Oct 2018 13:26:32 -0700 Subject: [PATCH] Added a "DrawMode" column to the prim tree-view widget to control the draw mode of models. The column is hidden by default, so it doesn't take up precious real estate in the browser when it's not necessary. This change promotes "PrimView" to a custom widget that derives from QTreeWidget, in preparation for moving most of the prim browser functionality into this (separate) module. It also includes several smaller improvements including: * Set elide mode of the propertyView QTreeWidget to ElideMiddle. * Renamed property inspector related methods and widgets to have "property" in the name instead of "attribute". (Internal change: 1905606) --- .../testUsdviewMetadatatabSelect.py | 4 +- .../testUsdviewVariantSelection.py | 6 +- pxr/usdImaging/lib/usdviewq/CMakeLists.txt | 1 + pxr/usdImaging/lib/usdviewq/appController.py | 75 ++++-- .../lib/usdviewq/headerContextMenu.py | 8 +- pxr/usdImaging/lib/usdviewq/mainWindowUI.ui | 21 +- pxr/usdImaging/lib/usdviewq/primTreeWidget.py | 236 ++++++++++++++++++ pxr/usdImaging/lib/usdviewq/primViewItem.py | 169 ++++++++++--- pxr/usdImaging/lib/usdviewq/usdviewstyle.qss | 10 +- pxr/usdImaging/lib/usdviewq/utils.cpp | 56 +++-- pxr/usdImaging/lib/usdviewq/utils.h | 6 +- pxr/usdImaging/lib/usdviewq/wrapUtils.cpp | 1 + 12 files changed, 496 insertions(+), 97 deletions(-) create mode 100644 pxr/usdImaging/lib/usdviewq/primTreeWidget.py diff --git a/pxr/usdImaging/bin/testusdview/testenv/testUsdviewMetadatatabSelect/testUsdviewMetadatatabSelect.py b/pxr/usdImaging/bin/testusdview/testenv/testUsdviewMetadatatabSelect/testUsdviewMetadatatabSelect.py index cc83a62de9..bbcfe584c1 100644 --- a/pxr/usdImaging/bin/testusdview/testenv/testUsdviewMetadatatabSelect/testUsdviewMetadatatabSelect.py +++ b/pxr/usdImaging/bin/testusdview/testenv/testUsdviewMetadatatabSelect/testUsdviewMetadatatabSelect.py @@ -27,7 +27,7 @@ def _testSelectionChangeScrollPosition(appController): # tab view does not affect the current scroll position in # the property view propView = appController._ui.propertyView - inspectorView = appController._ui.attributeInspector + inspectorView = appController._ui.propertyInspector initialScroll = propView.verticalScrollBar().value() @@ -52,7 +52,7 @@ def _testSelectionChangeScrollPosition(appController): assert propView.verticalScrollBar().value() == initialScroll def _testBasic(appController): - inspectorView = appController._ui.attributeInspector + inspectorView = appController._ui.propertyInspector inspectorView.setCurrentIndex(0) appController._mainWindow.repaint() diff --git a/pxr/usdImaging/bin/testusdview/testenv/testUsdviewVariantSelection/testUsdviewVariantSelection.py b/pxr/usdImaging/bin/testusdview/testenv/testUsdviewVariantSelection/testUsdviewVariantSelection.py index 5120186f97..d5ac8a0065 100644 --- a/pxr/usdImaging/bin/testusdview/testenv/testUsdviewVariantSelection/testUsdviewVariantSelection.py +++ b/pxr/usdImaging/bin/testusdview/testenv/testUsdviewVariantSelection/testUsdviewVariantSelection.py @@ -51,12 +51,12 @@ def _setupWidgets(appController): def _getVariantSelector(appController, whichVariant): # Select the metadata tab in the lower right corner - attributeInspector = appController._ui.attributeInspector - attributeInspector.setCurrentIndex(1) + propertyInspector = appController._ui.propertyInspector + propertyInspector.setCurrentIndex(1) # Grab the rows of our metadata tab and select the set containing # our variant selection - metadataTable = attributeInspector.currentWidget().findChildren(QtWidgets.QTableWidget)[0] + metadataTable = propertyInspector.currentWidget().findChildren(QtWidgets.QTableWidget)[0] for i in range(0, metadataTable.rowCount()): currentName = metadataTable.item(i,0).text() diff --git a/pxr/usdImaging/lib/usdviewq/CMakeLists.txt b/pxr/usdImaging/lib/usdviewq/CMakeLists.txt index 2748186a62..16f401186e 100644 --- a/pxr/usdImaging/lib/usdviewq/CMakeLists.txt +++ b/pxr/usdImaging/lib/usdviewq/CMakeLists.txt @@ -54,6 +54,7 @@ pxr_library(usdviewq common.py legendUtil.py primLegend.py + primTreeWidget.py propertyLegend.py attributeValueEditor.py overridableLineEdit.py diff --git a/pxr/usdImaging/lib/usdviewq/appController.py b/pxr/usdImaging/lib/usdviewq/appController.py index 2436516212..a755a8a608 100644 --- a/pxr/usdImaging/lib/usdviewq/appController.py +++ b/pxr/usdImaging/lib/usdviewq/appController.py @@ -44,6 +44,7 @@ from customAttributes import (_GetCustomAttributes, CustomAttribute, BoundingBoxAttribute, LocalToWorldXformAttribute, ResolvedBoundMaterial) +from primTreeWidget import PrimTreeWidget, PrimViewColumnIndex from primViewItem import PrimViewItem from variantComboBox import VariantComboBox from legendUtil import ToggleLegendWithBrowser @@ -139,7 +140,8 @@ def __init__(self, mainWindow, parent, name): self._mainWindow = mainWindow primViewColumnVisibility = self.stateProperty("primViewColumnVisibility", - default=[True, True, True], validator=lambda value: len(value) == 3) + default=[True, True, True, False], validator=lambda value: + len(value) == 4) propertyViewColumnVisibility = self.stateProperty("propertyViewColumnVisibility", default=[True, True, True], validator=lambda value: len(value) == 3) attributeInspectorCurrentTab = self.stateProperty("attributeInspectorCurrentTab", default=PropertyIndex.VALUE) @@ -182,7 +184,7 @@ def __init__(self, mainWindow, parent, name): propertyIndex = attributeInspectorCurrentTab if propertyIndex not in PropertyIndex: propertyIndex = PropertyIndex.VALUE - self._mainWindow._ui.attributeInspector.setCurrentIndex(propertyIndex) + self._mainWindow._ui.propertyInspector.setCurrentIndex(propertyIndex) def onSaveState(self, state): # UI is different when --norender is used so don't load the splitter sizes. @@ -220,7 +222,7 @@ def onSaveState(self, state): not self._mainWindow._ui.propertyView.isColumnHidden(c) for c in range(self._mainWindow._ui.propertyView.columnCount())] - state["attributeInspectorCurrentTab"] = self._mainWindow._ui.attributeInspector.currentIndex() + state["attributeInspectorCurrentTab"] = self._mainWindow._ui.propertyInspector.currentIndex() class Blocker: @@ -667,6 +669,8 @@ def __init__(self, parserData, resolverContextFn): nvh.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) nvh.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) nvh.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + nvh.resizeSection(3, 116) + nvh.setSectionResizeMode(3, QtWidgets.QHeaderView.Fixed) pvh = self._ui.propertyView.header() pvh.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) @@ -687,6 +691,7 @@ def __init__(self, parserData, resolverContextFn): QtCore.Qt.ScrollBarAlwaysOn) self._ui.attributeValueEditor.setAppController(self) + self._ui.primView.InitDrawModeDelegate(self) self._ui.currentPathWidget.editingFinished.connect( self._currentPathChanged) @@ -699,6 +704,7 @@ def __init__(self, parserData, resolverContextFn): primViewSelModel.selectionChanged.connect(self._selectionChanged) self._ui.primView.itemClicked.connect(self._itemClicked) + self._ui.primView.itemPressed.connect(self._itemPressed) self._ui.primView.header().customContextMenuRequested.connect( self._primViewHeaderContextMenu) @@ -816,8 +822,8 @@ def __init__(self, parserData, resolverContextFn): self._ui.actionCull_Backfaces.triggered.connect( self._toggleCullBackfaces) - self._ui.attributeInspector.currentChanged.connect( - self._updateAttributeInspector) + self._ui.propertyInspector.currentChanged.connect( + self._updatePropertyInspector) self._ui.propertyView.itemSelectionChanged.connect( self._propertyViewSelectionChanged) @@ -1571,7 +1577,7 @@ def _resetGUI(self): self._resetPrimView() self._updatePropertyView() - self._populateAttributeInspector() + self._populatePropertyInspector() self._updateMetadataView() self._updateLayerStackView() self._updateCompositionView() @@ -2523,10 +2529,10 @@ def _propSelectionChanged(self): Updates any UI that relies on the selection state. """ self._updatePropertyViewSelection() - self._populateAttributeInspector() - self._updateAttributeInspector() + self._populatePropertyInspector() + self._updatePropertyInspector() - def _populateAttributeInspector(self): + def _populatePropertyInspector(self): focusPrimPath = None focusPropName = None @@ -2549,11 +2555,11 @@ def _onCompositionSelectionChanged(self, curr=None, prev=None): self._currentSpec = getattr(curr, 'spec', None) self._currentLayer = getattr(curr, 'layer', None) - def _updateAttributeInspector(self, index=None, obj=None): + def _updatePropertyInspector(self, index=None, obj=None): # index must be the first parameter since this method is used as - # attributeInspector tab widget's currentChanged(int) signal callback + # propertyInspector tab widget's currentChanged(int) signal callback if index is None: - index = self._ui.attributeInspector.currentIndex() + index = self._ui.propertyInspector.currentIndex() if obj is None: obj = self._getSelectedObject() @@ -2720,7 +2726,8 @@ def _populateItem(self, prim, depth=0, maxDepth=0): # Create a new item. If we want its children we obviously # have to create those too. children = self._getFilteredChildren(prim) - item = PrimViewItem(prim, self, len(children) != 0) + item = PrimViewItem(prim, self, len(children) != 0, + parent=self._ui.primView) self._primToItemMap[prim] = item self._populateChildren(item, depth, maxDepth, children) # Push the item after the children so ancestors are processed @@ -2919,7 +2926,7 @@ def _primSelectionChanged(self, added, removed): self._updateHUDPrimStats() self._updateHUDGeomCounts() self._stageView.updateView() - self._updateAttributeInspector( + self._updatePropertyInspector( obj=self._dataModel.selection.getFocusPrim()) self._updatePropertyView() self._refreshAttributeValue() @@ -3033,16 +3040,19 @@ def _selectionChanged(self, added, removed): self._dataModel.selection.removePrim(prim) def _itemClicked(self, item, col): - # onClick() returns True if the click caused a state change (currently - # this will only be a change to visibility). - if item.onClick(col): + # toggleVis() returns True if the click caused a visibility change. + if col == PrimViewColumnIndex.VIS and item.toggleVis(): self.editComplete('Updated prim visibility') with Timer() as t: PrimViewItem.propagateVis(item) if self._printTiming: t.PrintTime("update vis column") - self._updateAttributeInspector( - obj=self._dataModel.selection.getFocusPrim()) + self._updatePropertyInspector( + obj=self._dataModel.selection.getFocusPrim()) + + def _itemPressed(self, item, col): + if col == PrimViewColumnIndex.DRAWMODE: + self._ui.primView.ShowDrawModeWidgetForItem(item) def _getPathsFromItems(self, items, prune = False): # this function returns a list of paths given a list of items if @@ -3216,7 +3226,7 @@ def _updatePropertyViewInternal(self): self._propertiesDict = self._getPropertiesDict() with self._propertyViewSelectionBlocker: treeWidget.clear() - self._populateAttributeInspector() + self._populatePropertyInspector() curPrimSelection = self._dataModel.selection.getFocusPrim() @@ -4587,6 +4597,31 @@ def _displayPurposeChanged(self): self._stageView.updateBboxPurposes() self._stageView.updateView() + def _resetPrimViewDrawMode(self, rootItem=None): + """Updates browser's "Draw Mode" columns. """ + with Timer() as t: + primView = self._ui.primView + primView.setUpdatesEnabled(False) + # Update draw-model for the entire prim tree if the given + # rootItem is None. + if rootItem is None: + rootItem = primView.invisibleRootItem().child(0) + if rootItem.childCount() == 0: + self._populateChildren(rootItem) + rootsToProcess = [rootItem.child(i) for i in + xrange(rootItem.childCount())] + for item in rootsToProcess: + PrimViewItem.propagateDrawMode(item, primView) + primView.setUpdatesEnabled(True) + if self._printTiming: + t.PrintTime("update draw mode column") + + def _drawModeChanged(self, primViewItem): + self._updatePropertyView() + self._resetPrimViewDrawMode(rootItem=primViewItem) + if self._stageView: + self._stageView.updateView() + def _HUDInfoChanged(self): """Called when a HUD setting that requires info refresh has changed.""" if self._isHUDVisible(): diff --git a/pxr/usdImaging/lib/usdviewq/headerContextMenu.py b/pxr/usdImaging/lib/usdviewq/headerContextMenu.py index 8f169ed3fc..4dc74e0491 100644 --- a/pxr/usdImaging/lib/usdviewq/headerContextMenu.py +++ b/pxr/usdImaging/lib/usdviewq/headerContextMenu.py @@ -59,7 +59,7 @@ def __init__(self, parent, column): self._parent = parent self._column = column - if parent.__class__ == QtWidgets.QTreeWidget: + if isinstance(parent, QtWidgets.QTreeWidget): self._text = parent.headerItem().text(column) else: self._text = parent.horizontalHeaderItem(column).text() @@ -69,16 +69,14 @@ def GetText(self): return self._text def IsEnabled(self): - return self._column > 0 + # Enable context menu item for columns except the "Name" column. + return 'Name' not in self.GetText() def IsChecked(self): # true if the column is visible, false otherwise return not self._parent.isColumnHidden(self._column) def RunCommand(self): - if self._column <= 0 : - return - # show or hide the column depending on its previous state self._parent.setColumnHidden(self._column, self.IsChecked()) diff --git a/pxr/usdImaging/lib/usdviewq/mainWindowUI.ui b/pxr/usdImaging/lib/usdviewq/mainWindowUI.ui index b71629e812..d937799448 100644 --- a/pxr/usdImaging/lib/usdviewq/mainWindowUI.ui +++ b/pxr/usdImaging/lib/usdviewq/mainWindowUI.ui @@ -162,7 +162,7 @@ - + Gotham Rounded @@ -229,6 +229,11 @@ AlignLeading|AlignVCenter + + + Draw Mode + + @@ -426,6 +431,9 @@ true + + Qt::ElideMiddle + 3 @@ -548,7 +556,7 @@ - + QFrame::NoFrame @@ -575,7 +583,7 @@ 2 - + QFrame::NoFrame @@ -596,7 +604,7 @@ 0 - + 0 @@ -2646,6 +2654,11 @@
propertyLegend
1 + + PrimTreeWidget + QTreeWidget +
primTreeWidget.h
+
diff --git a/pxr/usdImaging/lib/usdviewq/primTreeWidget.py b/pxr/usdImaging/lib/usdviewq/primTreeWidget.py new file mode 100644 index 0000000000..889baa08af --- /dev/null +++ b/pxr/usdImaging/lib/usdviewq/primTreeWidget.py @@ -0,0 +1,236 @@ +# +# Copyright 2018 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +from qt import QtCore, QtGui, QtWidgets +from constantGroup import ConstantGroup +from pxr import Sdf, Usd, UsdGeom +from primViewItem import PrimViewItem +from common import PrintWarning, Timer + +def _GetPropertySpecInSessionLayer(usdAttribute): + propertyStack = usdAttribute.GetPropertyStack(Usd.TimeCode.Default()) + if len(propertyStack) > 0: + stageSessionLayer = usdAttribute.GetStage().GetSessionLayer() + return stageSessionLayer.GetPropertyAtPath(usdAttribute.GetPath()) + return None + +class PrimViewColumnIndex(ConstantGroup): + NAME, TYPE, VIS, DRAWMODE = range(4) + +class DrawModes(ConstantGroup): + DEFAULT = "default" + CARDS = "cards" + BOUNDS = "bounds" + ORIGIN = "origin" + +class DrawModeComboBox(QtWidgets.QComboBox): + """ Specialize from QComboBox, so that we can send a signal when the pop-up + is hidden. + """ + signalPopupHidden = QtCore.Signal() + + def __init__(self, parent=None): + QtWidgets.QComboBox.__init__(self, parent) + + def hidePopup(self): + QtWidgets.QComboBox.hidePopup(self) + self.signalPopupHidden.emit() + +class DrawModeWidget(QtWidgets.QWidget): + """ This widget contains a combobox for selecting the draw mode and a + clear ('x') button for clearing an authored drawMode override in the + session layer. + """ + def __init__(self, primViewItem, refreshFunc, printTiming=False, + parent=None): + QtWidgets.QWidget.__init__(self, parent) + + self._primViewItem = primViewItem + self._layout = QtWidgets.QHBoxLayout() + self._layout.setSpacing(0) + self._layout.setContentsMargins(0,0,0,0) + self.setLayout(self._layout) + + self._comboBox = DrawModeComboBox(self) + self._modelAPI = UsdGeom.ModelAPI(self._primViewItem.prim) + # Reducing the width further causes the pop-up dialog to be trimmed + # and option text to be pruned. + self._comboBox.setFixedWidth(100) + self._comboBox.addItem(DrawModes.DEFAULT) + self._comboBox.addItem(DrawModes.CARDS) + self._comboBox.addItem(DrawModes.BOUNDS) + self._comboBox.addItem(DrawModes.ORIGIN) + self._layout.addWidget(self._comboBox) + + self._clearButton = QtWidgets.QToolButton(self) + self._clearButton.setText('X') + self._clearButton.setFixedSize(16, 16) + retainSizePolicy = self._clearButton.sizePolicy() + retainSizePolicy.setRetainSizeWhenHidden(True) + self._clearButton.setSizePolicy(retainSizePolicy) + self._layout.addWidget(self._clearButton) + + self._currentDrawMode = None + self.RefreshDrawMode() + self._firstPaint = True + + self._refreshFunc = refreshFunc + self._printTiming = printTiming + + self._comboBox.signalPopupHidden.connect(self._PopupHidden) + self._comboBox.activated.connect(self._UpdateDrawMode) + self._clearButton.clicked.connect(self._ClearDrawMode) + + def paintEvent(self, event): + # Virtual override of the paintEvent method on QWidget. + # Popup the combo box the first time the widget is drawn, since + # mouse-down in the column makes the widget appear. + # + # An alternative approach would be to set a timer in a constructor to + # pop open the combo box at the end of the event loop, but it causes a + # noticeable flicker in the UI. + if self._firstPaint: + self._comboBox.showPopup() + self._firstPaint = False + + # Invoke paintEvent on super class to do the actual painting of the + # widget. + super(DrawModeWidget, self).paintEvent(event) + + def _ShouldHideClearButton(self): + # Check if there's an authored value in the session layer. + drawModeAttr = self._modelAPI.GetModelDrawModeAttr() + + if drawModeAttr: + sessionSpec = _GetPropertySpecInSessionLayer(drawModeAttr) + if sessionSpec and sessionSpec.HasDefaultValue(): + return False + return True + + def RefreshDrawMode(self, currentDrawMode=None): + self._currentDrawMode = currentDrawMode if currentDrawMode else \ + self._modelAPI.ComputeModelDrawMode() + self._comboBox.setCurrentText(self._currentDrawMode) + + clearButtonIsHidden = self._clearButton.isHidden() + if self._ShouldHideClearButton(): + if not clearButtonIsHidden: + self._clearButton.hide() + self.update() + else: + if clearButtonIsHidden: + self._clearButton.show() + self.update() + + def _UpdateDrawMode(self): + newDrawModeSelection = str(self._comboBox.currentText()) + currentDrawMode = self._modelAPI.ComputeModelDrawMode() + if currentDrawMode != newDrawModeSelection: + with Timer() as t: + self._modelAPI.CreateModelDrawModeAttr().Set( + newDrawModeSelection) + + self.RefreshDrawMode(currentDrawMode=newDrawModeSelection) + + # We need to redraw the scene to pick up updates to draw mode. + self._refreshFunc(self._primViewItem) + if self._printTiming: + t.PrintTime("change model:drawMode on <%s> to %s" % + (self._modelAPI.GetPath(), newDrawModeSelection)) + self._CloseEditorIfNoEdit() + + def _ClearDrawMode(self): + with Timer() as t: + drawModeAttr = self._modelAPI.GetModelDrawModeAttr() + if drawModeAttr: + sessionSpec = _GetPropertySpecInSessionLayer(drawModeAttr) + if sessionSpec: + self._primViewItem.drawModeWidget = None + self._primViewItem.treeWidget().closePersistentEditor( + self._primViewItem, PrimViewColumnIndex.DRAWMODE) + + sessionSpec.ClearDefaultValue() + sessionSpec.layer.ScheduleRemoveIfInert(sessionSpec) + self._refreshFunc(self._primViewItem) + else: + PrintWarning(self._modelAPI.GetPath(), "Failed to get " + "session layer spec for the model:drawMode attribute") + return + else: + PrintWarning(self._modelAPI.GetPath(), "Failed to get " + "model:drawMode attribute") + return + if self._printTiming: + t.PrintTime("clear model:drawMode on <%s> to %s" % + (self._modelAPI.GetPath(), + self._comboBox.currentText())) + + def _CloseEditorIfNoEdit(self): + # If the clear button isn't present, then there's no edit. + if self._clearButton.isHidden(): + self._primViewItem.drawModeWidget = None + self._primViewItem.treeWidget().closePersistentEditor( + self._primViewItem, PrimViewColumnIndex.DRAWMODE) + + def _PopupHidden(self): + # Schedule closing the editor if no edit was made. + QtCore.QTimer.singleShot(0, self._CloseEditorIfNoEdit) + +class DrawModeItemDelegate(QtWidgets.QStyledItemDelegate): + def __init__(self, appController, parent=None): + QtWidgets.QStyledItemDelegate.__init__(self, parent=parent) + self._treeWidget = parent + self._appController = appController + + def createEditor(self, parent, option, index): + primViewItem = self._treeWidget.itemFromIndex(index) + + if not primViewItem.supportsDrawMode: + return None + + drawModeWidget = DrawModeWidget(primViewItem, + refreshFunc=self._appController._drawModeChanged, + printTiming=self._appController._printTiming, + parent=parent) + # Store a copy of the widget in the primViewItem, for use when + # propagating changes to draw mode down a prim hierarchy. + primViewItem.drawModeWidget = drawModeWidget + return drawModeWidget + +# This class extends QTreeWidget and is used to draw the prim tree view in +# usdview. +# More of the prim browser specific behavior needs to be migrated from +# appController into this class. +class PrimTreeWidget(QtWidgets.QTreeWidget): + def __init__(self, parent): + super(PrimTreeWidget, self).__init__(parent=parent) + self._appController = None + + def InitDrawModeDelegate(self, appController): + self._appController = appController + drawModeItemDelegate = DrawModeItemDelegate(appController, parent=self) + self.setItemDelegateForColumn(PrimViewColumnIndex.DRAWMODE, + drawModeItemDelegate) + + def ShowDrawModeWidgetForItem(self, primViewItem): + self.openPersistentEditor(primViewItem, PrimViewColumnIndex.DRAWMODE) diff --git a/pxr/usdImaging/lib/usdviewq/primViewItem.py b/pxr/usdImaging/lib/usdviewq/primViewItem.py index 73c1c65da4..74d6a88c58 100644 --- a/pxr/usdImaging/lib/usdviewq/primViewItem.py +++ b/pxr/usdImaging/lib/usdviewq/primViewItem.py @@ -21,13 +21,14 @@ # KIND, either express or implied. See the Apache License for the specific # language governing permissions and limitations under the Apache License. # -from qt import QtCore, QtWidgets -from pxr import Usd, UsdGeom +from qt import QtCore, QtGui, QtWidgets +from pxr import Sdf, Usd, UsdGeom from ._usdviewq import Utils from common import UIPrimTypeColors, UIFonts HALF_DARKER = 150 + # Pulled out as a wrapper to facilitate cprofile tracking def _GetPrimInfo(prim, time): return Utils.GetPrimInfo(prim, time) @@ -36,11 +37,11 @@ def _GetPrimInfo(prim, time): # prim data associated with it and populate itself with that data. class PrimViewItem(QtWidgets.QTreeWidgetItem): - def __init__(self, prim, appController, primHasChildren): + def __init__(self, prim, appController, primHasChildren, parent=None): # Do *not* pass a parent. The client must build the hierarchy. # This can dramatically improve performance when building a # large hierarchy. - super(PrimViewItem, self).__init__() + super(PrimViewItem, self).__init__(parent=parent) self.prim = prim self._appController = appController @@ -55,17 +56,21 @@ def __init__(self, prim, appController, primHasChildren): # If we know we'll have children show a norgie, otherwise don't. if primHasChildren: - self.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.ShowIndicator) + self.setChildIndicatorPolicy( + QtWidgets.QTreeWidgetItem.ShowIndicator) else: - self.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.DontShowIndicator) + self.setChildIndicatorPolicy( + QtWidgets.QTreeWidgetItem.DontShowIndicator) + # If this item includes a persistent drawMode widget, it is stored here. + self.drawModeWidget = None + def push(self): """Pushes prim data to the UI.""" # Push to UI. if self._needsPush: self._needsPush = False self._pull() - self.emitDataChanged() def _pull(self): """Extracts and stores prim data.""" @@ -84,6 +89,34 @@ def _pull(self): self.prim, self._appController._dataModel.currentFrame) self._extractInfo(info) + self.emitDataChanged() + + @staticmethod + def _HasAuthoredDrawMode(prim): + modelAPI = UsdGeom.ModelAPI(prim) + drawModeAttr = modelAPI.GetModelDrawModeAttr() + return drawModeAttr and drawModeAttr.HasAuthoredValueOpinion() + + def _isComputedDrawModeInherited(self, parentDrawModeIsInherited=None): + """Returns true if the computed draw mode for this item is inherited + from an authored "model:drawMode" value on an ancestor prim. + """ + if PrimViewItem._HasAuthoredDrawMode(self.prim): + return False + + parent = self.prim.GetParent() + while parent and parent.GetPath() != Sdf.Path.absoluteRootPath: + if PrimViewItem._HasAuthoredDrawMode(parent): + return True + + # Stop the upward traversal if we know whether the parent's draw + # mode is inherited. + if parentDrawModeIsInherited is not None: + return parentDrawModeIsInherited + + parent = parent.GetParent() + return False + def _extractInfo(self, info): ( self.hasArcs, self.active, @@ -92,21 +125,45 @@ def _extractInfo(self, info): self.abstract, self.isInMaster, self.isInstance, + self.supportsDrawMode, isVisibilityInherited, self.visVaries, self.name, self.typeName ) = info parent = self.parent() - self.computedVis = parent.computedVis \ - if isinstance(parent, PrimViewItem) \ - else UsdGeom.Tokens.inherited + parentIsPrimViewItem = isinstance(parent, PrimViewItem) + self.computedVis = parent.computedVis if parentIsPrimViewItem \ + else UsdGeom.Tokens.inherited if self.imageable and self.active: if isVisibilityInherited: self.vis = UsdGeom.Tokens.inherited else: self.vis = self.computedVis = UsdGeom.Tokens.invisible + # If this is the invisible root item, initialize fallback values for + # the drawMode related parameters. + if not parentIsPrimViewItem: + self.computedDrawMode = '' + self.isDrawModeInherited = False + return + + # We don't need to compute drawMode related parameters for primViewItems + # that don't support draw mode. + if not self.supportsDrawMode: + return + + self.computedDrawMode = UsdGeom.ModelAPI(self.prim).ComputeModelDrawMode( + parent.computedDrawMode) if parentIsPrimViewItem else '' + + + parentDrawModeIsInherited = parent.isDrawModeInherited if \ + parentIsPrimViewItem else None + + self.isDrawModeInherited = self._isComputedDrawModeInherited( + parentDrawModeIsInherited) + + def addChildren(self, children): """Adds children to the end of this item. This is the only method clients should call to manage an item's children.""" @@ -131,10 +188,25 @@ def data(self, column, role): result = self._typeData(role) elif column == 2: result = self._visData(role) + elif column == 3 and self.supportsDrawMode: + result = self._drawModeData(role) if not result: result = super(PrimViewItem, self).data(column, role) return result + def _GetForegroundColor(self): + self.push() + + if self.isInstance: + color = UIPrimTypeColors.INSTANCE + elif self.hasArcs: + color = UIPrimTypeColors.HAS_ARCS + elif self.isInMaster: + color = UIPrimTypeColors.MASTER + else: + color = UIPrimTypeColors.NORMAL + return color.color() if self.active else color.color().darker(HALF_DARKER) + def _nameData(self, role): if role == QtCore.Qt.DisplayRole: return self.name @@ -149,16 +221,7 @@ def _nameData(self, role): else: return UIFonts.DEFINED_PRIM elif role == QtCore.Qt.ForegroundRole: - if self.isInstance: - color = UIPrimTypeColors.INSTANCE - elif self.hasArcs: - color = UIPrimTypeColors.HAS_ARCS - elif self.isInMaster: - color = UIPrimTypeColors.MASTER - else: - color = UIPrimTypeColors.NORMAL - - return color if self.active else color.color().darker(HALF_DARKER) + return self._GetForegroundColor() elif role == QtCore.Qt.ToolTipRole: toolTip = 'Prim' if len(self.typeName) > 0: @@ -181,11 +244,27 @@ def _nameData(self, role): else: return None + def _drawModeData(self, role): + if role == QtCore.Qt.DisplayRole: + return self.computedDrawMode + elif role == QtCore.Qt.FontRole: + return UIFonts.BOLD_ITALIC if self.isDrawModeInherited else \ + UIFonts.DEFINED_PRIM + elif role == QtCore.Qt.ForegroundRole: + color = self._GetForegroundColor() + return color.darker(110) if self.isDrawModeInherited else \ + color + def _typeData(self, role): if role == QtCore.Qt.DisplayRole: return self.typeName else: return self._nameData(role) + + def _isVisInherited(self): + return self.imageable and self.active and \ + self.vis != UsdGeom.Tokens.invisible and \ + self.computedVis == UsdGeom.Tokens.invisible def _visData(self, role): if role == QtCore.Qt.DisplayRole: @@ -196,14 +275,12 @@ def _visData(self, role): elif role == QtCore.Qt.TextAlignmentRole: return QtCore.Qt.AlignCenter elif role == QtCore.Qt.FontRole: - return UIFonts.BOLD + return UIFonts.BOLD_ITALIC if self._isVisInherited() \ + else UIFonts.BOLD elif role == QtCore.Qt.ForegroundRole: - fgColor = self._nameData(role) - if (self.imageable and self.active and - self.vis != UsdGeom.Tokens.invisible and - self.computedVis == UsdGeom.Tokens.invisible): - fgColor = fgColor.color().darker() - return fgColor + fgColor = self._GetForegroundColor() + return fgColor.darker() if self._isVisInherited() \ + else fgColor else: return None @@ -260,6 +337,35 @@ def loadStateChanged(self): # do a better job of partial updates. self._appController.updateGUI() + @staticmethod + def propagateDrawMode(item, primView, parentDrawMode='', + parentDrawModeIsInherited=None): + # If this item does not support draw mode, none of its descendants + # can support it. Hence, stop recursion here. + # + # Call push() here to ensure that supportsDrawMode has been populated + # for the item. + item.push() + if not item.supportsDrawMode: + return + + from primTreeWidget import DrawModeWidget + drawModeWidget = item.drawModeWidget + if drawModeWidget: + drawModeWidget.RefreshDrawMode() + else: + modelAPI = UsdGeom.ModelAPI(item.prim) + item.computedDrawMode = modelAPI.ComputeModelDrawMode(parentDrawMode) + item.isDrawModeInherited = item._isComputedDrawModeInherited( + parentDrawModeIsInherited=parentDrawModeIsInherited) + item.emitDataChanged() + + # Traverse down to children to update their drawMode. + for child in [item.child(i) for i in xrange(item.childCount())]: + PrimViewItem.propagateDrawMode(child, primView, + parentDrawMode=item.computedDrawMode, + parentDrawModeIsInherited=item.isDrawModeInherited) + @staticmethod def propagateVis(item, authoredVisHasChanged=True): parent = item.parent() @@ -275,7 +381,6 @@ def propagateVis(item, authoredVisHasChanged=True): for child in [item.child(i) for i in xrange(item.childCount())]: child._pushVisRecursive(inheritedVis, authoredVisHasChanged) - def _resetAncestorsRecursive(self, authoredVisHasChanged): parent = self.parent() inheritedVis = parent._resetAncestorsRecursive(authoredVisHasChanged) \ @@ -333,11 +438,9 @@ def visChanged(self): # we must re-determine if visibility is varying over time self.loadVis(self.parent().computedVis, True) - def onClick(self, col): - """Return True if the click caused the prim to change state (visibility, - etc)""" - if col == 2 and self.imageable and self.active: + def toggleVis(self): + """Return True if the the prim's visibility state was toggled. """ + if self.imageable and self.active: self.setVisible(self.vis == UsdGeom.Tokens.invisible) return True return False - diff --git a/pxr/usdImaging/lib/usdviewq/usdviewstyle.qss b/pxr/usdImaging/lib/usdviewq/usdviewstyle.qss index 6d3104c7b9..b8fd8f741e 100644 --- a/pxr/usdImaging/lib/usdviewq/usdviewstyle.qss +++ b/pxr/usdImaging/lib/usdviewq/usdviewstyle.qss @@ -541,14 +541,14 @@ QComboBox { height: 22px; background: rgb(41, 41, 41); border:none; - border-radius: 7px; + border-radius: 5px; padding: 1px 0px 1px 3px; /*This makes text colour work*/ } QComboBox::drop-down { background: rgb(41, 41, 41); border:none; - border-radius: 7px; + border-radius: 5px; } /** @@ -562,7 +562,7 @@ QComboBox::drop-down { border-top: 2px solid rgb(32, 32, 32); } -#attributeBrowserFrame, #attributeInspectorFrame { +#attributeBrowserFrame, #propertyInspectorFrame { border-bottom: 2px solid rgb(32, 32, 32); } @@ -570,11 +570,11 @@ QComboBox::drop-down { border-left: 2px solid rgb(32, 32, 32); } -#glFrame, #attributeInspectorFrame { +#glFrame, #propertyInspectorFrame { border-right: 2px solid rgb(32, 32, 32); } -#attributeInspectorContainer { +#propertyInspectorContainer { background: rgb(78, 80, 84); } diff --git a/pxr/usdImaging/lib/usdviewq/utils.cpp b/pxr/usdImaging/lib/usdviewq/utils.cpp index 620854f5e0..c1269428fa 100644 --- a/pxr/usdImaging/lib/usdviewq/utils.cpp +++ b/pxr/usdImaging/lib/usdviewq/utils.cpp @@ -27,6 +27,7 @@ #include "pxr/usd/usd/attribute.h" #include "pxr/usd/usd/attributeQuery.h" +#include "pxr/usd/usd/modelAPI.h" #include "pxr/usd/usd/prim.h" #include "pxr/usd/usd/schemaBase.h" #include "pxr/usd/usd/stage.h" @@ -77,46 +78,53 @@ TF_DEFINE_PRIVATE_TOKENS( (root) ); -/*static*/ -UsdviewqUtils::PrimInfo -UsdviewqUtils::GetPrimInfo(UsdPrim prim, UsdTimeCode time) +UsdviewqUtils::PrimInfo::PrimInfo(const UsdPrim &prim, const UsdTimeCode time) { - PrimInfo info; - - info.hasCompositionArcs = (prim.HasAuthoredReferences() || - prim.HasPayload() || - prim.HasAuthoredInherits() || - prim.HasAuthoredSpecializes() || - prim.HasVariantSets()); - info.isActive = prim.IsActive(); + hasCompositionArcs = (prim.HasAuthoredReferences() || + prim.HasPayload() || + prim.HasAuthoredInherits() || + prim.HasAuthoredSpecializes() || + prim.HasVariantSets()); + isActive = prim.IsActive(); UsdGeomImageable img(prim); - info.isImageable = static_cast(img); - info.isDefined = prim.IsDefined(); - info.isAbstract = prim.IsAbstract(); + isImageable = static_cast(img); + isDefined = prim.IsDefined(); + isAbstract = prim.IsAbstract(); + // isInMaster is meant to guide UI to consider the prim's "source", // so even if the prim is a proxy prim, then unlike the core // UsdPrim.IsInMaster(), we want to consider it as coming from a master // to make it visually distinctive. If in future we need to decouple // the two concepts we can, but we're sensitive here to python marshalling // costs. - info.isInMaster = prim.IsInMaster() || prim.IsInstanceProxy(); - info.isInstance = prim.IsInstance(); - info.isVisibilityInherited = false; + isInMaster = prim.IsInMaster() || prim.IsInstanceProxy(); + + supportsDrawMode = isActive && isDefined && + !isInMaster && prim.GetPath() != SdfPath::AbsoluteRootPath() && + UsdModelAPI(prim).IsModel(); + + isInstance = prim.IsInstance(); + isVisibilityInherited = false; if (img){ UsdAttributeQuery query(img.GetVisibilityAttr()); TfToken visibility = UsdGeomTokens->inherited; query.Get(&visibility, time); - info.isVisibilityInherited = (visibility == UsdGeomTokens->inherited); - info.visVaries = query.ValueMightBeTimeVarying(); + isVisibilityInherited = (visibility == UsdGeomTokens->inherited); + visVaries = query.ValueMightBeTimeVarying(); } if (prim.GetParent()) - info.name = prim.GetName().GetString(); + name = prim.GetName().GetString(); else - info.name = _tokens->root.GetString(); - info.typeName = prim.GetTypeName().GetString(); - - return info; + name = _tokens->root.GetString(); + typeName = prim.GetTypeName().GetString(); +} + +/*static*/ +UsdviewqUtils::PrimInfo +UsdviewqUtils::GetPrimInfo(const UsdPrim &prim, const UsdTimeCode time) +{ + return PrimInfo(prim, time); } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/usdImaging/lib/usdviewq/utils.h b/pxr/usdImaging/lib/usdviewq/utils.h index 8c2a412187..8162d4edb5 100644 --- a/pxr/usdImaging/lib/usdviewq/utils.h +++ b/pxr/usdImaging/lib/usdviewq/utils.h @@ -44,6 +44,8 @@ class UsdviewqUtils { public: struct PrimInfo { + PrimInfo(const UsdPrim &prim, const UsdTimeCode time); + bool hasCompositionArcs; bool isActive; bool isImageable; @@ -51,6 +53,7 @@ class UsdviewqUtils { bool isAbstract; bool isInMaster; bool isInstance; + bool supportsDrawMode; bool isVisibilityInherited; bool visVaries; std::string name; @@ -72,7 +75,8 @@ class UsdviewqUtils { /// population. Takes a time argument so that we can evaluate the prim's /// visibiity if it is imageable. USDVIEWQ_API - static UsdviewqUtils::PrimInfo GetPrimInfo(UsdPrim prim, UsdTimeCode time); + static UsdviewqUtils::PrimInfo GetPrimInfo(const UsdPrim &prim, + const UsdTimeCode time); }; diff --git a/pxr/usdImaging/lib/usdviewq/wrapUtils.cpp b/pxr/usdImaging/lib/usdviewq/wrapUtils.cpp index c82d68888e..e40a60e936 100644 --- a/pxr/usdImaging/lib/usdviewq/wrapUtils.cpp +++ b/pxr/usdImaging/lib/usdviewq/wrapUtils.cpp @@ -55,6 +55,7 @@ _GetPrimInfo(UsdPrim const &prim, UsdTimeCode time) info.isAbstract, info.isInMaster, info.isInstance, + info.supportsDrawMode, info.isVisibilityInherited, info.visVaries, info.name,