diff --git a/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py b/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py index 40040f3b88..1440646645 100644 --- a/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py +++ b/lib/mayaUsd/resources/scripts/mayaUsdLibRegisterStrings.py @@ -73,6 +73,15 @@ def mayaUsdLibRegisterStrings(): register('kTextVariant', 'Variant') register('kTextVariantToolTip','If selected, your Maya reference will be defined in a variant. This will enable your prim to\nhave 2 variants you can switch between in the Outliner; the Maya reference and its USD cache.') + register('kAddRefOrPayloadPrimPathToolTip', 'Specifying a prim path will make an explicit reference to a prim.\n' + + 'If there is no default prim in your chosen reference, this field auto populates with the top level prim for you to reference in.\n' + + 'If the field is left blank, no prim will be referenced.') + register('kAddRefOrPayloadPrimPathLabel', 'Prim Path') + register('kAddRefOrPayloadPrimPathPlaceHolder', ' (Default Prim)') + register('kAddRefOrPayloadPrimPathHelpLabel', 'Help on Select a Prim for Reference') + register('kAddRefOrPayloadPrimPathTitle', 'Select a Prim to Reference') + register('kAddRefOrPayloadSelectLabel', 'Select') + # mayaUsdClearRefsOrPayloadsOptions.py register('kClearRefsOrPayloadsOptionsTitle', 'Clear All USD References/Payloads') register('kClearRefsOrPayloadsOptionsMessage', 'Clear all references/payloads on %s?') diff --git a/lib/mayaUsd/resources/scripts/mayaUsdMayaReferenceUtils.py b/lib/mayaUsd/resources/scripts/mayaUsdMayaReferenceUtils.py index e96d660a8e..6d54508a4e 100644 --- a/lib/mayaUsd/resources/scripts/mayaUsdMayaReferenceUtils.py +++ b/lib/mayaUsd/resources/scripts/mayaUsdMayaReferenceUtils.py @@ -16,6 +16,7 @@ import maya.cmds as cmds from mayaUsdLibRegisterStrings import getMayaUsdLibString +from pxr import Sdf # These names should not be localized as Usd only accepts [a-z,A-Z] as valid characters. kDefaultMayaReferencePrimName = 'MayaReference1' @@ -27,13 +28,15 @@ compositionArcReference = 'Reference' _compositionArcValues = [compositionArcReference, compositionArcPayload] -listEditTypeKey = '' +listEditTypeKey = 'listEditType' listEditTypePrepend = 'Prepend' listEditTypeAppend = 'Append' _listEditedAsValues = [listEditTypePrepend, listEditTypeAppend] loadPayloadKey = 'loadPayload' +referencedPrimPathKey = 'referencedPrimPath' + def defaultMayaReferencePrimName(): return kDefaultMayaReferencePrimName @@ -118,7 +121,84 @@ def _compositionArcChanged(selectedItem): enableLoadPayload = bool(compositionArc == compositionArcPayload) cmds.checkBoxGrp('loadPayload', edit=True, enable=enableLoadPayload) -def createUsdRefOrPayloadUI(showLoadPayload=False): +def _selectReferencedPrim(*args): + """ + Open a dialog to select a prim from the currently selected file + to be the referenced prim. + """ + filename = _getCurrentFilename() + primPath = cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', query=True, text=True) + title = getMayaUsdLibString('kAddRefOrPayloadPrimPathTitle') + helpLabel = getMayaUsdLibString('kAddRefOrPayloadPrimPathHelpLabel') + helpToken = 'something something' + result = cmds.usdImportDialog(filename, hideVariants=True, hideRoot=True, primPath=primPath, title=title, helpLabel=helpLabel, helpToken=helpToken) + if result: + primPath = cmds.usdImportDialog(query=True, primPath=True) + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, text=primPath) + cmds.usdImportDialog(clearData=True) + +_currentFileName = None + +def _setCurrentFilename(filename): + """Sets the current file selection.""" + global _currentFileName + _currentFileName = filename + +def _getCurrentFilename(): + """Retrieve the current file selection.""" + global _currentFileName + return _currentFileName + +def _resetReferencedPrim(*args): + """Reset the referenced prim UI""" + _updateReferencedPrimBasedOnFile() + +def _getDefaultAndRootPrims(filename): + """Retrieve the default and first root prims of a USD file.""" + defPrim, rootPrim = None, None + try: + layer = Sdf.Layer.FindOrOpen(filename) + if layer: + # Note: the root prims at the USD layer level are SdfPrimSpec, + # so they are not SdfPath themselves nor prim. That is + # why their path is retrieved via their path property. + # + # The default prim is a pure token though, because it is + # a metadata on the layer, so it can be used as-is. + rootPrims = layer.rootPrims + rootPrim = rootPrims[0].path if len(rootPrims) > 0 else None + defPrim = layer.defaultPrim + except Exception as ex: + print(str(ex)) + + return defPrim, rootPrim + +def _updateReferencedPrimBasedOnFile(): + """Update all UI related to the referenced prim based on the currently selected file.""" + filename = _getCurrentFilename() + if not filename: + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, text='', placeholderText='') + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, enable=False) + cmds.button('mayaUsdAddRefOrPayloadFilePathBrowser', edit=True, enable=False) + cmds.symbolButton('mayaUsdAddRefOrPayloadFilePathReset', edit=True, enable=False) + return + + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, enable=True) + cmds.button('mayaUsdAddRefOrPayloadFilePathBrowser', edit=True, enable=True) + cmds.symbolButton('mayaUsdAddRefOrPayloadFilePathReset', edit=True, enable=True) + + defaultPrim, rootPrim = _getDefaultAndRootPrims(filename) + if defaultPrim: + placeHolder = defaultPrim + getMayaUsdLibString('kAddRefOrPayloadPrimPathPlaceHolder') + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, text='', placeholderText=placeHolder) + elif rootPrim: + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, text=rootPrim, placeholderText='') + else: + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', edit=True, text='', placeholderText='') + +def createUsdRefOrPayloadUI(uiForLoad=False): + _setCurrentFilename(None) + with SetParentContext(cmds.rowLayout(numberOfColumns=2)): tooltip = getMayaUsdLibString('kOptionAsUSDReferenceToolTip') cmds.optionMenuGrp('compositionArcTypeMenu', @@ -136,27 +216,48 @@ def createUsdRefOrPayloadUI(showLoadPayload=False): for label in listEditedAsLabels: cmds.menuItem(label=label) - if showLoadPayload: + if uiForLoad: + with SetParentContext(cmds.rowLayout(numberOfColumns=3, adjustableColumn1=True)): + tooltip = getMayaUsdLibString('kAddRefOrPayloadPrimPathToolTip') + primPathLabel = getMayaUsdLibString("kAddRefOrPayloadPrimPathLabel") + selectLabel = getMayaUsdLibString("kAddRefOrPayloadSelectLabel") + cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', label=primPathLabel, ann=tooltip) + cmds.button('mayaUsdAddRefOrPayloadFilePathBrowser', label=selectLabel, ann=tooltip) + cmds.symbolButton('mayaUsdAddRefOrPayloadFilePathReset', image="refresh.png", ann=tooltip) + cmds.checkBoxGrp('loadPayload', label=getMayaUsdLibString('kOptionLoadPayload'), annotation=getMayaUsdLibString('kLoadPayloadAnnotation'), ncb=1) -def initUsdRefOrPayloadUI(values, showLoadPayload=False): +def initUsdRefOrPayloadUI(values, uiForLoad=False): compositionArcMenuIndex = _getMenuIndex(_compositionArcValues, values[compositionArcKey]) cmds.optionMenuGrp('compositionArcTypeMenu', edit=True, select=compositionArcMenuIndex) listEditTypeMenuIndex = _getMenuIndex(_listEditedAsValues,values[listEditTypeKey]) cmds.optionMenu('listEditedAsMenu', edit=True, select=listEditTypeMenuIndex) - if showLoadPayload: + if uiForLoad: cmds.optionMenuGrp('compositionArcTypeMenu', edit=True, cc=_compositionArcChanged) _compositionArcChanged(compositionArcMenuIndex) cmds.checkBoxGrp('loadPayload', edit=True, value1=values[loadPayloadKey]) -def commitUsdRefOrPayloadUI(showLoadPayload=False): + cmds.button("mayaUsdAddRefOrPayloadFilePathBrowser", c=_selectReferencedPrim, edit=True) + cmds.button("mayaUsdAddRefOrPayloadFilePathReset", c=_resetReferencedPrim, edit=True) + + _updateReferencedPrimBasedOnFile() + +def updateUsdRefOrPayloadUI(selectedFile): + if selectedFile == _getCurrentFilename(): + return + _setCurrentFilename(selectedFile) + _updateReferencedPrimBasedOnFile() + +def commitUsdRefOrPayloadUI(uiForLoad=False): values = {} values[compositionArcKey] = _getMenuGrpValue('compositionArcTypeMenu', _compositionArcValues) values[listEditTypeKey ] = _getMenuValue('listEditedAsMenu', _listEditedAsValues) - values[loadPayloadKey ] = cmds.checkBoxGrp('loadPayload', query=True, value1=True) if showLoadPayload else True + values[loadPayloadKey ] = cmds.checkBoxGrp('loadPayload', query=True, value1=True) if uiForLoad else True + values[referencedPrimPathKey] = cmds.textFieldGrp('mayaUsdAddRefOrPayloadPrimPath', query=True, text=True) if uiForLoad else '' + return values diff --git a/lib/mayaUsd/resources/scripts/mayaUsdUtils.py b/lib/mayaUsd/resources/scripts/mayaUsdUtils.py index 803192d5b1..e304a04b7b 100644 --- a/lib/mayaUsd/resources/scripts/mayaUsdUtils.py +++ b/lib/mayaUsd/resources/scripts/mayaUsdUtils.py @@ -211,6 +211,16 @@ def saveWantPayloadLoaded(want): opVarName = "mayaUsd_WantPayloadLoaded" cmds.optionVar(iv=(opVarName, want)) +def getReferencedPrimPath(): + opVarName = "mayaUsd_ReferencedPrimPath" + if not cmds.optionVar(exists=opVarName): + return '' + return cmds.optionVar(query=opVarName) + +def saveReferencedPrimPath(primPath): + opVarName = "mayaUsd_ReferencedPrimPath" + cmds.optionVar(sv=(opVarName, primPath)) + def showHelpMayaUSD(contentId): """ Helper method to display help content. diff --git a/lib/mayaUsd/ufe/MayaUsdContextOps.cpp b/lib/mayaUsd/ufe/MayaUsdContextOps.cpp index 1d5ef496bf..544407888d 100644 --- a/lib/mayaUsd/ufe/MayaUsdContextOps.cpp +++ b/lib/mayaUsd/ufe/MayaUsdContextOps.cpp @@ -184,6 +184,7 @@ const char* _selectUSDFileScript() string $result[] = `fileDialog2 -fileMode 1 -caption "Add USD Reference/Payload to Prim" + -okCaption Reference -fileFilter "USD Files (%s);;%s" -optionsUICreate addUSDReferenceCreateUi -optionsUIInit addUSDReferenceInitUi @@ -555,10 +556,12 @@ Ufe::UndoableCommand::Ptr MayaUsdContextOps::doOpCmd(const ItemPath& itemPath) if (path.empty()) return nullptr; - const bool asRef = UsdMayaUtilFileSystem::wantReferenceCompositionArc(); - const bool prepend = UsdMayaUtilFileSystem::wantPrependCompositionArc(); + const std::string primPath = UsdMayaUtilFileSystem::getReferencedPrimPath(); + const bool asRef = UsdMayaUtilFileSystem::wantReferenceCompositionArc(); + const bool prepend = UsdMayaUtilFileSystem::wantPrependCompositionArc(); if (asRef) { - return std::make_shared(prim(), path, prepend); + return std::make_shared( + prim(), path, primPath, prepend); } else { Ufe::UndoableCommand::Ptr preloadCmd; const bool preload = UsdMayaUtilFileSystem::wantPayloadLoaded(); @@ -569,8 +572,8 @@ Ufe::UndoableCommand::Ptr MayaUsdContextOps::doOpCmd(const ItemPath& itemPath) preloadCmd = std::make_shared(prim()); } - auto payloadCmd - = std::make_shared(prim(), path, prepend); + auto payloadCmd = std::make_shared( + prim(), path, primPath, prepend); auto compoCmd = std::make_shared(); compoCmd->append(preloadCmd); diff --git a/lib/mayaUsd/utils/utilFileSystem.cpp b/lib/mayaUsd/utils/utilFileSystem.cpp index bce8debdfa..7c6db5b768 100644 --- a/lib/mayaUsd/utils/utilFileSystem.cpp +++ b/lib/mayaUsd/utils/utilFileSystem.cpp @@ -422,6 +422,14 @@ bool UsdMayaUtilFileSystem::wantPayloadLoaded() && MGlobal::optionVarIntValue(WANT_PAYLOAD_LOADED); } +std::string UsdMayaUtilFileSystem::getReferencedPrimPath() +{ + static const MString WANT_REFERENCE_COMPOSITION_ARC = "mayaUsd_ReferencedPrimPath"; + if (!MGlobal::optionVarExists(WANT_REFERENCE_COMPOSITION_ARC)) + return {}; + return MGlobal::optionVarStringValue(WANT_REFERENCE_COMPOSITION_ARC).asChar(); +} + const char* getScenesFolderScript = R"( global proc string UsdMayaUtilFileSystem_GetScenesFolder() { diff --git a/lib/mayaUsd/utils/utilFileSystem.h b/lib/mayaUsd/utils/utilFileSystem.h index 86b403da1d..9a89251fc1 100644 --- a/lib/mayaUsd/utils/utilFileSystem.h +++ b/lib/mayaUsd/utils/utilFileSystem.h @@ -171,6 +171,11 @@ bool wantPrependCompositionArc(); MAYAUSD_CORE_PUBLIC bool wantPayloadLoaded(); +/*! \brief returns the prim path referenced by the USD reference or payload. + */ +MAYAUSD_CORE_PUBLIC +std::string getReferencedPrimPath(); + /*! \brief prepares the UI used to save layers, so that the UI can potentially make the selected file name relative to the given directory. */ diff --git a/lib/usd/ui/importDialog/ItemDelegate.cpp b/lib/usd/ui/importDialog/ItemDelegate.cpp index 0cf14d3df2..14bc02e3f7 100644 --- a/lib/usd/ui/importDialog/ItemDelegate.cpp +++ b/lib/usd/ui/importDialog/ItemDelegate.cpp @@ -85,7 +85,11 @@ void ItemDelegate::paint( // painter->drawRect(QRect(0, option.rect.y(), option.rect.right(), option.rect.bottom())); // painter->restore(); - ParentClass::paint(painter, option, index); + QStyleOptionViewItem adjustedOption(option); + adjustedOption.decorationPosition = QStyleOptionViewItem::Right; + adjustedOption.decorationAlignment = Qt::AlignLeft | Qt::AlignVCenter; + + ParentClass::paint(painter, adjustedOption, index); } void ItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const diff --git a/lib/usd/ui/importDialog/TreeItem.cpp b/lib/usd/ui/importDialog/TreeItem.cpp index d2cd266d2c..2049ce98e1 100644 --- a/lib/usd/ui/importDialog/TreeItem.cpp +++ b/lib/usd/ui/importDialog/TreeItem.cpp @@ -25,44 +25,53 @@ namespace MAYAUSD_NS_DEF { -QPixmap* TreeItem::fsCheckBoxOn = nullptr; -QPixmap* TreeItem::fsCheckBoxOnDisabled = nullptr; -QPixmap* TreeItem::fsCheckBoxOff = nullptr; -QPixmap* TreeItem::fsCheckBoxOffDisabled = nullptr; +const QPixmap* TreeItem::fsCheckBoxOn = nullptr; +const QPixmap* TreeItem::fsCheckBoxOnDisabled = nullptr; +const QPixmap* TreeItem::fsCheckBoxOff = nullptr; +const QPixmap* TreeItem::fsCheckBoxOffDisabled = nullptr; -TreeItem::TreeItem(const UsdPrim& prim, Type t) noexcept +TreeItem::TreeItem(const UsdPrim& prim, bool isDefaultPrim, Column column) noexcept : ParentClass() , fPrim(prim) - , fType(t) + , fColumn(column) , fCheckState(CheckState::kChecked_Disabled) , fVariantSelectionModified(false) { - initializeItem(); + initializeItem(isDefaultPrim); } UsdPrim TreeItem::prim() const { return fPrim; } int TreeItem::type() const { return ParentClass::ItemType::UserType; } +const QPixmap* TreeItem::createPixmap(const char* pixmapURL) const +{ + const QPixmap* pixmap = nullptr; + + const TreeModel* treeModel = qobject_cast(model()); + if (treeModel != nullptr) { + pixmap = treeModel->mayaQtUtil().createPixmap(pixmapURL); + } else { + // The tree model should never be null, but we can recover here if it is. + TF_RUNTIME_ERROR("Unexpected null tree model"); + pixmap = new QPixmap(pixmapURL); + } + + // If the resource fails to load, return a non-null pointer. + static const QPixmap empty; + if (!pixmap) + pixmap = ∅ + + return pixmap; +} + const QPixmap& TreeItem::checkImage() const { if (fsCheckBoxOn == nullptr) { - const TreeModel* treeModel = qobject_cast(model()); - if (treeModel != nullptr) { - fsCheckBoxOn = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOn.png"); - fsCheckBoxOnDisabled - = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOnDisabled.png"); - fsCheckBoxOff = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOff.png"); - fsCheckBoxOffDisabled - = treeModel->mayaQtUtil().createPixmap(":/ImportDialog/checkboxOffDisabled.png"); - } else { - // The tree model should never be null, but we can recover here if it is. - TF_RUNTIME_ERROR("Unexpected null tree model"); - fsCheckBoxOn = new QPixmap(":/ImportDialog/checkboxOn.png"); - fsCheckBoxOnDisabled = new QPixmap(":/ImportDialog/checkboxOnDisabled.png"); - fsCheckBoxOff = new QPixmap(":/ImportDialog/checkboxOff.png"); - fsCheckBoxOffDisabled = new QPixmap(":/ImportDialog/checkboxOffDisabled.png"); - } + fsCheckBoxOn = createPixmap(":/ImportDialog/checkboxOn.png"); + fsCheckBoxOnDisabled = createPixmap(":/ImportDialog/checkboxOnDisabled.png"); + fsCheckBoxOff = createPixmap(":/ImportDialog/checkboxOff.png"); + fsCheckBoxOffDisabled = createPixmap(":/ImportDialog/checkboxOffDisabled.png"); } switch (fCheckState) { @@ -76,30 +85,34 @@ const QPixmap& TreeItem::checkImage() const void TreeItem::setCheckState(TreeItem::CheckState st) { - assert(fType == Type::kLoad); - if (fType == Type::kLoad) + assert(fColumn == kColumnLoad); + if (fColumn == kColumnLoad) fCheckState = st; } void TreeItem::setVariantSelectionModified() { - assert(fType == Type::kVariants); - if (fType == Type::kVariants) + assert(fColumn == kColumnVariants); + if (fColumn == kColumnVariants) fVariantSelectionModified = true; } -void TreeItem::initializeItem() +void TreeItem::initializeItem(bool isDefaultPrim) { - switch (fType) { - case Type::kLoad: fCheckState = CheckState::kChecked_Disabled; break; - case Type::kName: + switch (fColumn) { + case kColumnLoad: fCheckState = CheckState::kChecked_Disabled; break; + case kColumnName: if (fPrim.IsPseudoRoot()) setText("Root"); else setText(QString::fromStdString(fPrim.GetName().GetString())); + if (isDefaultPrim) { + if (const QPixmap* pixmap = TreeModel::getDefaultPrimPixmap()) + setData(*pixmap, Qt::DecorationRole); + } break; - case Type::kType: setText(QString::fromStdString(fPrim.GetTypeName().GetString())); break; - case Type::kVariants: { + case kColumnType: setText(QString::fromStdString(fPrim.GetTypeName().GetString())); break; + case kColumnVariants: { if (fPrim.HasVariantSets()) { // We set a special role flag when this prim has variant sets. // So we know when to create the label and combo box(es) for the variant diff --git a/lib/usd/ui/importDialog/TreeItem.h b/lib/usd/ui/importDialog/TreeItem.h index 39727b01ad..7db4ab6542 100644 --- a/lib/usd/ui/importDialog/TreeItem.h +++ b/lib/usd/ui/importDialog/TreeItem.h @@ -39,12 +39,13 @@ class MAYAUSD_UI_PUBLIC TreeItem : public QStandardItem public: using ParentClass = QStandardItem; - enum class Type + enum Column { - kLoad, - kName, - kType, - kVariants + kColumnLoad, + kColumnName, + kColumnType, + kColumnVariants, + kColumnLast }; enum class CheckState @@ -60,7 +61,7 @@ class MAYAUSD_UI_PUBLIC TreeItem : public QStandardItem * \param prim The USD Prim to represent with this item. * \param text Column text to display on the View of the the Qt TreeModel. */ - explicit TreeItem(const UsdPrim& prim, Type t) noexcept; + explicit TreeItem(const UsdPrim& prim, bool isDefaultPrim, Column column) noexcept; /** * \brief Destructor. @@ -102,14 +103,15 @@ class MAYAUSD_UI_PUBLIC TreeItem : public QStandardItem void resetVariantSelectionModified() { fVariantSelectionModified = false; } private: - void initializeItem(); + void initializeItem(bool isDefaultPrim); + const QPixmap* createPixmap(const char* pixmapURL) const; protected: // The USD Prim that the item represents in the TreeModel. UsdPrim fPrim; - // The type of this item. - Type fType; + // The column of this item. + Column fColumn; // For the LOAD column, the check state. CheckState fCheckState; @@ -117,10 +119,10 @@ class MAYAUSD_UI_PUBLIC TreeItem : public QStandardItem // Special flag set when the variant selection was modified. bool fVariantSelectionModified; - static QPixmap* fsCheckBoxOn; - static QPixmap* fsCheckBoxOnDisabled; - static QPixmap* fsCheckBoxOff; - static QPixmap* fsCheckBoxOffDisabled; + static const QPixmap* fsCheckBoxOn; + static const QPixmap* fsCheckBoxOnDisabled; + static const QPixmap* fsCheckBoxOff; + static const QPixmap* fsCheckBoxOffDisabled; }; } // namespace MAYAUSD_NS_DEF diff --git a/lib/usd/ui/importDialog/TreeModel.cpp b/lib/usd/ui/importDialog/TreeModel.cpp index 299265bbc3..54f98240d9 100644 --- a/lib/usd/ui/importDialog/TreeModel.cpp +++ b/lib/usd/ui/importDialog/TreeModel.cpp @@ -15,6 +15,8 @@ // #include "TreeModel.h" +#include "USDImportDialog.h" + #include #include #include @@ -30,12 +32,14 @@ namespace MAYAUSD_NS_DEF { namespace { -TreeItem* -findTreeItem(TreeModel* treeModel, const QModelIndex& parent, std::function fn) +TreeItem* findTreeItem( + const TreeModel* treeModel, + const QModelIndex& parent, + std::function fn) { for (int r = 0; r < treeModel->rowCount(parent); ++r) { // Note: only the load column (0) has children, so we use it when looking for children. - QModelIndex childIndex = treeModel->index(r, TreeModel::kTreeColumn_Load, parent); + QModelIndex childIndex = treeModel->index(r, TreeItem::kColumnLoad, parent); TreeItem* item = static_cast(treeModel->itemFromIndex(childIndex)); if (fn(item)) { return item; @@ -76,14 +80,14 @@ void resetVariantToPrimSelection(TreeItem* variantItem) void resetAllVariants(TreeModel* treeModel, const QModelIndex& parent) { for (int r = 0; r < treeModel->rowCount(parent); ++r) { - QModelIndex variantIndex = treeModel->index(r, TreeModel::kTreeColumn_Variants, parent); + QModelIndex variantIndex = treeModel->index(r, TreeItem::kColumnVariants, parent); TreeItem* variantItem = static_cast(treeModel->itemFromIndex(variantIndex)); if (nullptr != variantItem && variantItem->variantSelectionModified()) { resetVariantToPrimSelection(variantItem); } - QModelIndex childIndex = treeModel->index(r, TreeModel::kTreeColumn_Load, parent); + QModelIndex childIndex = treeModel->index(r, TreeItem::kColumnLoad, parent); if (treeModel->hasChildren(childIndex)) { resetAllVariants(treeModel, childIndex); } @@ -92,14 +96,22 @@ void resetAllVariants(TreeModel* treeModel, const QModelIndex& parent) } // namespace +const QPixmap* TreeModel::fsDefaultPrimImage = nullptr; + TreeModel::TreeModel( - const IMayaMQtUtil& mayaQtUtil, - const ImportData* importData /*= nullptr*/, - QObject* parent /*= nullptr*/) noexcept + const IMayaMQtUtil& mayaQtUtil, + const ImportData* importData, + const USDImportDialogOptions& options, + QObject* parent /*= nullptr*/) noexcept : ParentClass { parent } , fImportData { importData } , fMayaQtUtil { mayaQtUtil } + , fShowVariants(options.showVariants) + , fShowRoot(options.showRoot) { + if (!fsDefaultPrimImage) { + fsDefaultPrimImage = fMayaQtUtil.createPixmap(":/ImportDialog/defaultPrim.png"); + } } QVariant TreeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const @@ -109,7 +121,7 @@ QVariant TreeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole* TreeItem* item = static_cast(itemFromIndex(index)); - if ((role == Qt::DecorationRole) && (index.column() == kTreeColumn_Load)) + if ((role == Qt::DecorationRole) && (index.column() == TreeItem::kColumnLoad)) return item->checkImage(); return ParentClass::data(index, role); @@ -123,7 +135,7 @@ Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const // The base class implementation returns a combination of flags that enables // the item (ItemIsEnabled) and allows it to be selected (ItemIsSelectable). Qt::ItemFlags flags = ParentClass::flags(index); - if (index.column() == kTreeColumn_Load) + if (index.column() == TreeItem::kColumnLoad) flags &= ~Qt::ItemIsSelectable; return flags; @@ -152,7 +164,7 @@ void TreeModel::setChildCheckState(const QModelIndex& parent, TreeItem::CheckSta int rMin = -1, rMax = -1; for (int r = 0; r < rowCount(parent); ++r) { // Note: only the load column (0) has children, so we use it when looking for children. - QModelIndex childIndex = this->index(r, kTreeColumn_Load, parent); + QModelIndex childIndex = this->index(r, TreeItem::kColumnLoad, parent); TreeItem* item = static_cast(itemFromIndex(childIndex)); // If child item state matches the input, no need to recurse. @@ -167,8 +179,8 @@ void TreeModel::setChildCheckState(const QModelIndex& parent, TreeItem::CheckSta setChildCheckState(childIndex, state); } } - QModelIndex rMinIndex = this->index(rMin, kTreeColumn_Load, parent); - QModelIndex rMaxIndex = this->index(rMax, kTreeColumn_Load, parent); + QModelIndex rMinIndex = this->index(rMin, TreeItem::kColumnLoad, parent); + QModelIndex rMaxIndex = this->index(rMax, TreeItem::kColumnLoad, parent); QVector roles; roles << Qt::DecorationRole; Q_EMIT dataChanged(rMinIndex, rMaxIndex, roles); @@ -192,7 +204,7 @@ void TreeModel::fillStagePopulationMask(UsdStagePopulationMask& popMask, const Q { for (int r = 0; r < rowCount(parent); ++r) { // Note: only the load column (0) has children, so we use it when looking for children. - QModelIndex childIndex = this->index(r, kTreeColumn_Load, parent); + QModelIndex childIndex = this->index(r, TreeItem::kColumnLoad, parent); TreeItem* item = static_cast(itemFromIndex(childIndex)); if (item->checkState() == TreeItem::CheckState::kChecked) { auto primPath = item->prim().GetPath(); @@ -212,8 +224,11 @@ void TreeModel::fillPrimVariantSelections( ImportData::PrimVariantSelections& primVariantSelections, const QModelIndex& parent) { + if (!fShowVariants) + return; + for (int r = 0; r < rowCount(parent); ++r) { - QModelIndex variantIndex = this->index(r, kTreeColumn_Variants, parent); + QModelIndex variantIndex = this->index(r, TreeItem::kColumnVariants, parent); TreeItem* item = static_cast(itemFromIndex(variantIndex)); if (item->variantSelectionModified()) { // Note: both the variant name and variant selection roles contain a @@ -236,7 +251,7 @@ void TreeModel::fillPrimVariantSelections( } // Note: only the load column (0) has children, so we use it when looking for children. - QModelIndex childIndex = this->index(r, kTreeColumn_Load, parent); + QModelIndex childIndex = this->index(r, TreeItem::kColumnLoad, parent); if (hasChildren(childIndex)) { fillPrimVariantSelections(primVariantSelections, childIndex); } @@ -245,8 +260,11 @@ void TreeModel::fillPrimVariantSelections( void TreeModel::openPersistentEditors(QTreeView* tv, const QModelIndex& parent) { + if (!fShowVariants) + return; + for (int r = 0; r < rowCount(parent); ++r) { - QModelIndex varSelIndex = this->index(r, kTreeColumn_Variants, parent); + QModelIndex varSelIndex = this->index(r, TreeItem::kColumnVariants, parent); int type = varSelIndex.data(ItemDelegate::kTypeRole).toInt(); if (type == ItemDelegate::kVariants) { QSortFilterProxyModel* proxyModel = qobject_cast(tv->model()); @@ -254,7 +272,7 @@ void TreeModel::openPersistentEditors(QTreeView* tv, const QModelIndex& parent) } // Note: only the load column (0) has children, so we use it when looking for children. - QModelIndex childIndex = this->index(r, kTreeColumn_Load, parent); + QModelIndex childIndex = this->index(r, TreeItem::kColumnLoad, parent); if (hasChildren(childIndex)) { openPersistentEditors(tv, childIndex); } @@ -308,6 +326,27 @@ void TreeModel::checkEnableItem(TreeItem* item) } } +TreeItem* TreeModel::findPathItem(const PXR_NS::SdfPath& path) const +{ + auto fnFindRoot = [path](TreeItem* item) -> bool { + if (item->prim().GetPath() == path) + return true; + return false; + }; + return findTreeItem(this, QModelIndex(), fnFindRoot); +} + +TreeItem* TreeModel::findPrimItem(const UsdPrim& prim) const +{ + return findPathItem(prim.GetPath()); +} + +TreeItem* TreeModel::getFirstItem() const +{ + QModelIndex childIndex = index(0, TreeItem::kColumnLoad, QModelIndex()); + return static_cast(itemFromIndex(childIndex)); +} + void TreeModel::updateCheckedItemCount() const { int nbChecked = 0, nbVariantsModified = 0; @@ -327,7 +366,7 @@ void TreeModel::countCheckedItems( for (int r = 0; r < rowCount(parent); ++r) { TreeItem* item; - QModelIndex checkedChildIndex = this->index(r, kTreeColumn_Load, parent); + QModelIndex checkedChildIndex = this->index(r, TreeItem::kColumnLoad, parent); item = static_cast(itemFromIndex(checkedChildIndex)); const TreeItem::CheckState state = item->checkState(); @@ -335,12 +374,14 @@ void TreeModel::countCheckedItems( || TreeItem::CheckState::kChecked_Disabled == state) { nbChecked++; - // We are only counting modified variants of in-scope prims - QModelIndex variantChildIndex = this->index(r, kTreeColumn_Variants, parent); - item = static_cast(itemFromIndex(variantChildIndex)); + if (fShowVariants) { + // We are only counting modified variants of in-scope prims + QModelIndex variantChildIndex = this->index(r, TreeItem::kColumnVariants, parent); + item = static_cast(itemFromIndex(variantChildIndex)); - if (item->variantSelectionModified()) { - nbVariantsModified++; + if (item->variantSelectionModified()) { + nbVariantsModified++; + } } } @@ -359,7 +400,7 @@ void TreeModel::updateModifiedVariantCount() const void TreeModel::onItemClicked(TreeItem* item) { - if (item->index().column() == kTreeColumn_Load) { + if (item->index().column() == TreeItem::kColumnLoad) { // We only allow toggling an enabled checked or unchecked item. TreeItem::CheckState st = item->checkState(); if (st == TreeItem::CheckState::kChecked) { @@ -370,6 +411,15 @@ void TreeModel::onItemClicked(TreeItem* item) } } -void TreeModel::resetVariants() { resetAllVariants(this, QModelIndex()); } +void TreeModel::resetVariants() +{ + if (!fShowVariants) + return; + + resetAllVariants(this, QModelIndex()); +} + +/*static*/ +const QPixmap* TreeModel::getDefaultPrimPixmap() { return fsDefaultPrimImage; } } // namespace MAYAUSD_NS_DEF diff --git a/lib/usd/ui/importDialog/TreeModel.h b/lib/usd/ui/importDialog/TreeModel.h index 6a913751cd..b2ffd8d48e 100644 --- a/lib/usd/ui/importDialog/TreeModel.h +++ b/lib/usd/ui/importDialog/TreeModel.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -33,6 +34,7 @@ PXR_NAMESPACE_USING_DIRECTIVE namespace MAYAUSD_NS_DEF { class IMayaMQtUtil; +struct USDImportDialogOptions; /** * \brief Qt Model to explore the hierarchy of a USD file. @@ -51,27 +53,15 @@ class MAYAUSD_UI_PUBLIC TreeModel : public QStandardItemModel * \param parent A reference to the parent of the TreeModel. */ explicit TreeModel( - const IMayaMQtUtil& mayaQtUtil, - const ImportData* importData = nullptr, - QObject* parent = nullptr) noexcept; + const IMayaMQtUtil& mayaQtUtil, + const ImportData* importData, + const USDImportDialogOptions& options, + QObject* parent = nullptr) noexcept; // QStandardItemModel overrides QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; - /** - * \brief Order of the columns as they appear in the Tree. - * \remarks The order of the enumeration is important. - */ - enum TREE_COLUMNS - { - kTreeColumn_Load, // Should we load this prim? - kTreeColumn_Name, // Name of the item as it appears in the TreeView. - kTreeColumn_Type, // Type of the primitive. - kTreeColumn_Variants, // Variant Set(s) and Variant Selection of the primitive. - kTreeColumn_Last // Last element of the enum. - }; - void setRootPrimPath(const std::string& path); void getRootPrimPath(std::string&, const QModelIndex& parent); void fillStagePopulationMask(UsdStagePopulationMask& popMask, const QModelIndex& parent); @@ -87,10 +77,16 @@ class MAYAUSD_UI_PUBLIC TreeModel : public QStandardItemModel void resetVariants(); -private: void uncheckEnableTree(); void checkEnableItem(TreeItem* item); + TreeItem* findPrimItem(const PXR_NS::UsdPrim& prim) const; + TreeItem* findPathItem(const PXR_NS::SdfPath& path) const; + TreeItem* getFirstItem() const; + + static const QPixmap* getDefaultPrimPixmap(); + +private: void updateCheckedItemCount() const; void countCheckedItems(const QModelIndex& parent, int& nbChecked, int& nbVariantsModified) const; @@ -111,6 +107,13 @@ public Q_SLOTS: // Special interface we can use to perform Maya Qt utilities (such as Pixmap loading). const IMayaMQtUtil& fMayaQtUtil; + + bool fShowVariants; + bool fShowRoot; + + // Need to be in the tree model becasue we need to create it before + // the tree item have their model set. + static const QPixmap* fsDefaultPrimImage; }; } // namespace MAYAUSD_NS_DEF diff --git a/lib/usd/ui/importDialog/TreeModelFactory.cpp b/lib/usd/ui/importDialog/TreeModelFactory.cpp index ed1f3291e6..1e5e5b4b1b 100644 --- a/lib/usd/ui/importDialog/TreeModelFactory.cpp +++ b/lib/usd/ui/importDialog/TreeModelFactory.cpp @@ -15,6 +15,8 @@ // #include "TreeModelFactory.h" +#include "USDImportDialog.h" + #include #include #include @@ -40,72 +42,112 @@ static_assert( /*static*/ std::unique_ptr TreeModelFactory::createEmptyTreeModel( - const IMayaMQtUtil& mayaQtUtil, - const ImportData* importData /*= nullptr*/, - QObject* parent /*= nullptr*/) + const IMayaMQtUtil& mayaQtUtil, + const ImportData* importData, + const USDImportDialogOptions& options, + QObject* parent /*= nullptr*/) { - std::unique_ptr treeModel(new TreeModel(mayaQtUtil, importData, parent)); - treeModel->setHorizontalHeaderLabels({ QObject::tr(""), - QObject::tr("Prim Name"), - QObject::tr("Prim Type"), - QObject::tr("Variant Set and Variant") }); + std::unique_ptr treeModel(new TreeModel(mayaQtUtil, importData, options, parent)); + + QStringList headerLabels({ QObject::tr(""), QObject::tr("Name"), QObject::tr("Type") }); + if (options.showVariants) + headerLabels.append(QObject::tr("Variant Set and Variant")); + + treeModel->setHorizontalHeaderLabels(headerLabels); return treeModel; -} +} // namespace MAYAUSD_NS_DEF /*static*/ std::unique_ptr TreeModelFactory::createFromStage( - const UsdStageRefPtr& stage, - const IMayaMQtUtil& mayaQtUtil, - const ImportData* importData /*= nullptr*/, - QObject* parent, - int* nbItems /*= nullptr*/ + const UsdStageRefPtr& stage, + const IMayaMQtUtil& mayaQtUtil, + const ImportData* importData, + const USDImportDialogOptions& options, + QObject* parent, + int* nbItems /*= nullptr*/ ) { - std::unique_ptr treeModel = createEmptyTreeModel(mayaQtUtil, importData, parent); - int cnt = buildTreeHierarchy(stage->GetPseudoRoot(), treeModel->invisibleRootItem()); + std::unique_ptr treeModel + = createEmptyTreeModel(mayaQtUtil, importData, options, parent); + + UsdPrim rootPrim = stage->GetPseudoRoot(); + UsdPrim defPrim = stage->GetDefaultPrim(); + + const int cnt = options.showRoot + ? buildTreeHierarchy(rootPrim, defPrim, treeModel->invisibleRootItem(), options) + : buildTreeChildren(rootPrim, defPrim, treeModel->invisibleRootItem(), options); + if (nbItems != nullptr) *nbItems = cnt; + + TreeItem* item = treeModel->findPrimItem(defPrim); + if (!item) + item = treeModel->getFirstItem(); + if (item) + treeModel->checkEnableItem(item); + return treeModel; } /*static*/ -QList TreeModelFactory::createPrimRow(const UsdPrim& prim) +QList TreeModelFactory::createPrimRow( + const UsdPrim& prim, + const UsdPrim& defaultPrim, + const USDImportDialogOptions& options) { + const bool isDefaultPrim = (prim == defaultPrim); // Cache the values to be displayed, in order to avoid querying the USD Prim too frequently // (despite it being cached and optimized for frequent access). Avoiding frequent conversions // from USD Strings to Qt Strings helps in keeping memory allocations low. - QList ret = { new TreeItem(prim, TreeItem::Type::kLoad), - new TreeItem(prim, TreeItem::Type::kName), - new TreeItem(prim, TreeItem::Type::kType), - new TreeItem(prim, TreeItem::Type::kVariants) }; + QList ret = { new TreeItem(prim, isDefaultPrim, TreeItem::kColumnLoad), + new TreeItem(prim, isDefaultPrim, TreeItem::kColumnName), + new TreeItem(prim, isDefaultPrim, TreeItem::kColumnType) }; + + if (options.showVariants) { + ret.append(new TreeItem(prim, isDefaultPrim, TreeItem::kColumnVariants)); + } return ret; } /*static*/ -int TreeModelFactory::buildTreeHierarchy(const UsdPrim& prim, QStandardItem* parentItem) +int TreeModelFactory::buildTreeHierarchy( + const UsdPrim& prim, + const UsdPrim& defaultPrim, + QStandardItem* parentItem, + const USDImportDialogOptions& options) { - QList primDataCells = createPrimRow(prim); + QList primDataCells = createPrimRow(prim, defaultPrim, options); parentItem->appendRow(primDataCells); - int cnt = 1; + return 1 + buildTreeChildren(prim, defaultPrim, primDataCells.front(), options); +} - for (const auto& childPrim : prim.GetAllChildren()) { - cnt += buildTreeHierarchy(childPrim, primDataCells.front()); - } +/*static*/ +int TreeModelFactory::buildTreeChildren( + const UsdPrim& prim, + const UsdPrim& defaultPrim, + QStandardItem* parentItem, + const USDImportDialogOptions& options) +{ + int cnt = 0; + for (const auto& childPrim : prim.GetAllChildren()) + cnt += buildTreeHierarchy(childPrim, defaultPrim, parentItem, options); return cnt; } /*static*/ int TreeModelFactory::buildTreeHierarchy( - const UsdPrim& prim, - QStandardItem* parentItem, - const unordered_sdfpath_set& primsToIncludeInTree, - size_t& insertionsRemaining) + const UsdPrim& prim, + const UsdPrim& defaultPrim, + QStandardItem* parentItem, + const USDImportDialogOptions& options, + const unordered_sdfpath_set& primsToIncludeInTree, + size_t& insertionsRemaining) { int cnt = 0; bool primShouldBeIncluded = primsToIncludeInTree.find(prim.GetPath()) != primsToIncludeInTree.end(); if (primShouldBeIncluded) { - QList primDataCells = createPrimRow(prim); + QList primDataCells = createPrimRow(prim, defaultPrim, options); parentItem->appendRow(primDataCells); ++cnt; @@ -114,7 +156,12 @@ int TreeModelFactory::buildTreeHierarchy( if (--insertionsRemaining > 0) { for (const auto& childPrim : prim.GetAllChildren()) { cnt += buildTreeHierarchy( - childPrim, primDataCells.front(), primsToIncludeInTree, insertionsRemaining); + childPrim, + defaultPrim, + primDataCells.front(), + options, + primsToIncludeInTree, + insertionsRemaining); } } } diff --git a/lib/usd/ui/importDialog/TreeModelFactory.h b/lib/usd/ui/importDialog/TreeModelFactory.h index 29745860f0..b523877fed 100644 --- a/lib/usd/ui/importDialog/TreeModelFactory.h +++ b/lib/usd/ui/importDialog/TreeModelFactory.h @@ -38,6 +38,7 @@ namespace MAYAUSD_NS_DEF { class IMayaMQtUtil; class TreeModel; class ImportData; +struct USDImportDialogOptions; /** * \brief Factory to create a tree-like structure of USD content suitable to be displayed in a @@ -58,9 +59,10 @@ class MAYAUSD_UI_PUBLIC TreeModelFactory * \return An empty TreeModel. */ static std::unique_ptr createEmptyTreeModel( - const IMayaMQtUtil& mayaQtUtil, - const ImportData* importData = nullptr, - QObject* parent = nullptr); + const IMayaMQtUtil& mayaQtUtil, + const ImportData* importData, + const USDImportDialogOptions& options, + QObject* parent = nullptr); /** * \brief Create a TreeModel from the given USD Stage. @@ -70,11 +72,12 @@ class MAYAUSD_UI_PUBLIC TreeModelFactory * \return A TreeModel created from the given USD Stage. */ static std::unique_ptr createFromStage( - const UsdStageRefPtr& stage, - const IMayaMQtUtil& mayaQtUtil, - const ImportData* importData = nullptr, - QObject* parent = nullptr, - int* nbItems = nullptr); + const UsdStageRefPtr& stage, + const IMayaMQtUtil& mayaQtUtil, + const ImportData* importData, + const USDImportDialogOptions& options, + QObject* parent = nullptr, + int* nbItems = nullptr); protected: // Type definition for an STL unordered set of SDF Paths: @@ -85,7 +88,10 @@ class MAYAUSD_UI_PUBLIC TreeModelFactory * \param prim The USD Prim for which to create the list of data cells. * \return The List of data cells used to represent the given USD Prim's data in the tree. */ - static QList createPrimRow(const UsdPrim& prim); + static QList createPrimRow( + const UsdPrim& prim, + const UsdPrim& defaultPrim, + const USDImportDialogOptions& options); /** * \brief Build the tree hierarchy starting at the given USD Prim. @@ -93,7 +99,24 @@ class MAYAUSD_UI_PUBLIC TreeModelFactory * \param parentItem The parent into which to attach the tree hierarchy. * \return The number of items added. */ - static int buildTreeHierarchy(const UsdPrim& prim, QStandardItem* parentItem); + static int buildTreeHierarchy( + const UsdPrim& prim, + const UsdPrim& defaultPrim, + QStandardItem* parentItem, + const USDImportDialogOptions& options); + + /** + * \brief Build the tree hierarchy for teh chidlren of the given USD Prim. + * \param prim The USD Prim from which to extract the children. + * \param parentItem The parent into which to attach the tree hierarchy. + * \return The number of items added. + */ + static int buildTreeChildren( + const UsdPrim& prim, + const UsdPrim& defaultPrim, + QStandardItem* parentItem, + const USDImportDialogOptions& options); + /** * \brief Build the tree hierarchy starting at the given USD Prim. * \param prim The USD Prim from which to start building the tree hierarchy. @@ -103,10 +126,12 @@ class MAYAUSD_UI_PUBLIC TreeModelFactory * \return The number of items added. */ static int buildTreeHierarchy( - const UsdPrim& prim, - QStandardItem* parentItem, - const unordered_sdfpath_set& primsToIncludeInTree, - size_t& insertionsRemaining); + const UsdPrim& prim, + const UsdPrim& defaultPrim, + QStandardItem* parentItem, + const USDImportDialogOptions& options, + const unordered_sdfpath_set& primsToIncludeInTree, + size_t& insertionsRemaining); }; } // namespace MAYAUSD_NS_DEF diff --git a/lib/usd/ui/importDialog/USDImportDialog.cpp b/lib/usd/ui/importDialog/USDImportDialog.cpp index 34c77f6d38..d09d2cedf9 100644 --- a/lib/usd/ui/importDialog/USDImportDialog.cpp +++ b/lib/usd/ui/importDialog/USDImportDialog.cpp @@ -31,11 +31,13 @@ namespace MAYAUSD_NS_DEF { IUSDImportView::~IUSDImportView() { } USDImportDialog::USDImportDialog( - const std::string& filename, - const ImportData* importData, - const IMayaMQtUtil& mayaQtUtil, - QWidget* parent /*= nullptr*/) + const std::string& filename, + const ImportData* importData, + const USDImportDialogOptions& options, + const IMayaMQtUtil& mayaQtUtil, + QWidget* parent /*= nullptr*/) : QDialog { parent } + , fOptions(options) , fUI { new Ui::ImportDialog() } , fStage { UsdStage::Open(filename, UsdStage::InitialLoadSet::LoadAll) } , fFilename { filename } @@ -45,12 +47,14 @@ USDImportDialog::USDImportDialog( throw std::invalid_argument("Invalid filename passed to USD Import Dialog"); fUI->setupUi(this); + applyOptions(); // If we were given some import data we will only use it if it matches our // input filename. In the case where the user opened dialog, clicked Apply and // then reopens the dialog we want to reset the dialog to the previous state. const ImportData* matchingImportData = nullptr; - if ((importData != nullptr) && (fFilename == importData->filename())) { + if ((importData != nullptr) && (fFilename == importData->filename()) + && importData->rootPrimPath().size() > 0) { fRootPrimPath = importData->rootPrimPath(); matchingImportData = importData; } @@ -66,8 +70,8 @@ USDImportDialog::USDImportDialog( // These calls must come after the UI is initialized via "setupUi()": int nbItems = 0; - fTreeModel - = TreeModelFactory::createFromStage(fStage, mayaQtUtil, matchingImportData, this, &nbItems); + fTreeModel = TreeModelFactory::createFromStage( + fStage, mayaQtUtil, matchingImportData, fOptions, this, &nbItems); fProxyModel = std::unique_ptr(new QSortFilterProxyModel(this)); QObject::connect( fTreeModel.get(), SIGNAL(checkedStateChanged(int)), this, SLOT(onCheckedStateChanged(int))); @@ -88,7 +92,7 @@ USDImportDialog::USDImportDialog( fProxyModel->setDynamicSortFilter(false); fProxyModel->setFilterCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); fUI->treeView->setModel(fProxyModel.get()); - fUI->treeView->setTreePosition(TreeModel::kTreeColumn_Name); + fUI->treeView->setTreePosition(TreeItem::kColumnName); fUI->treeView->setAlternatingRowColors(true); fUI->treeView->setSelectionMode(QAbstractItemView::SingleSelection); QObject::connect( @@ -133,9 +137,11 @@ USDImportDialog::USDImportDialog( constexpr int kTypeWidth = 120; constexpr int kNameWidth = 500; header->setMinimumSectionSize(kLoadWidth); - header->resizeSection(TreeModel::kTreeColumn_Load, kLoadWidth); - header->resizeSection(TreeModel::kTreeColumn_Name, kNameWidth); - header->resizeSection(TreeModel::kTreeColumn_Type, kTypeWidth); + header->resizeSection(TreeItem::kColumnLoad, kLoadWidth); + header->resizeSection(TreeItem::kColumnName, kNameWidth); + if (fOptions.showVariants) { + header->resizeSection(TreeItem::kColumnType, kTypeWidth); + } header->setSectionResizeMode(0, QHeaderView::Fixed); // Display the full path of the file to import: @@ -145,6 +151,23 @@ USDImportDialog::USDImportDialog( fUI->applyButton->setEnabled(true); } +void USDImportDialog::applyOptions() +{ + if (fOptions.title.size() > 0) { + setWindowTitle(fOptions.title.c_str()); + } + + if (fOptions.helpLabel.size() > 0) { + fUI->actionHelp_on_Hierarchy_View->setText( + QCoreApplication::translate("ImportDialog", fOptions.helpLabel.c_str(), nullptr)); + } + + fUI->nbVariantsChanged->setVisible(fOptions.showVariants); + fUI->nbVariantsChangedLabel->setVisible(fOptions.showVariants); + + fUI->selectPrims->setVisible(fOptions.showHeaderMessage); +} + USDImportDialog::~USDImportDialog() { // Note: the destructor is needed here so we can forward declare the Ui::ImportDialog @@ -218,8 +241,10 @@ void USDImportDialog::onResetFileTriggered() void USDImportDialog::onHierarchyViewHelpTriggered() { - MGlobal::executePythonCommand( - "from mayaUsdUtils import showHelpMayaUSD; showHelpMayaUSD(\"UsdHierarchyView\");"); + MString url = fOptions.helpURL.size() > 0 ? fOptions.helpURL.c_str() : "UsdHierarchyView"; + MString cmd; + cmd.format("from mayaUsdUtils import showHelpMayaUSD; showHelpMayaUSD('^1s');", url); + MGlobal::executePythonCommand(cmd); } void USDImportDialog::onCheckedStateChanged(int nbChecked) diff --git a/lib/usd/ui/importDialog/USDImportDialog.h b/lib/usd/ui/importDialog/USDImportDialog.h index 18bca6e95e..777cf13285 100644 --- a/lib/usd/ui/importDialog/USDImportDialog.h +++ b/lib/usd/ui/importDialog/USDImportDialog.h @@ -40,6 +40,19 @@ namespace MAYAUSD_NS_DEF { class IMayaMQtUtil; +/** + * \brief Options for the USD file import dialog. + */ +struct MAYAUSD_UI_PUBLIC USDImportDialogOptions +{ + std::string title; + std::string helpLabel; + std::string helpURL; + bool showVariants = true; + bool showRoot = true; + bool showHeaderMessage = true; +}; + /** * \brief USD file import dialog. */ @@ -56,10 +69,11 @@ class MAYAUSD_UI_PUBLIC USDImportDialog * \param parent A reference to the parent widget of the dialog. */ explicit USDImportDialog( - const std::string& filename, - const ImportData* importData, - const IMayaMQtUtil& mayaQtUtil, - QWidget* parent = nullptr); + const std::string& filename, + const ImportData* importData, + const USDImportDialogOptions& options, + const IMayaMQtUtil& mayaQtUtil, + QWidget* parent = nullptr); //! Destructor. ~USDImportDialog(); @@ -83,6 +97,11 @@ private Q_SLOTS: void onModifiedVariantsChanged(int); protected: + void applyOptions(); + + // The options for the dialog. + USDImportDialogOptions fOptions; + // Reference to the Qt UI View of the dialog: std::unique_ptr fUI; diff --git a/lib/usd/ui/importDialog/USDImportDialog.ui b/lib/usd/ui/importDialog/USDImportDialog.ui index 8ab6bc64dd..85f5298460 100644 --- a/lib/usd/ui/importDialog/USDImportDialog.ui +++ b/lib/usd/ui/importDialog/USDImportDialog.ui @@ -102,7 +102,7 @@ true - Apply + OK @@ -146,7 +146,7 @@ - Total Number of Prims in Scope: + Prims in selected scope: @@ -200,9 +200,9 @@ - + - Variants Switched in Scope: + Variants switched in selected scope: diff --git a/lib/usd/ui/importDialog/USDImportDialogCmd.cpp b/lib/usd/ui/importDialog/USDImportDialogCmd.cpp index 87d698fb8e..300f6b4d5d 100644 --- a/lib/usd/ui/importDialog/USDImportDialogCmd.cpp +++ b/lib/usd/ui/importDialog/USDImportDialogCmd.cpp @@ -62,6 +62,17 @@ constexpr auto kPrimCountFlagLong = "-primCount"; constexpr auto kSwitchedVariantCountFlag = "-swc"; constexpr auto kSwitchedVariantCountFlagLong = "-switchedVariantCount"; +constexpr auto kHideRoot = "-hr"; +constexpr auto kHideRootLong = "-hideRoot"; +constexpr auto kHideVariantsFlag = "-hv"; +constexpr auto kHideVariantsFlagLong = "-hideVariants"; +constexpr auto kWindowTitleFlag = "-ti"; +constexpr auto kWindowTitleFlagLong = "-title"; +constexpr auto kHelpLabelFlag = "-hl"; +constexpr auto kHelpLabelFlagLong = "-helpLabel"; +constexpr auto kHelpTokenFlag = "-ht"; +constexpr auto kHelpTokenFlagLong = "-helpToken"; + constexpr auto kParentWindowFlag = "-pw"; constexpr auto kParentWindowFlagLong = "-parentWindow"; @@ -160,6 +171,31 @@ MStatus USDImportDialogCmd::applyToProxy(const MString& proxyPath) return MS::kSuccess; } +static MString getStringArg(const MArgParser& argData, MStatus& status) +{ + MStringArray array; + status = argData.getObjects(array); + if (!status) + return {}; + + if (array.length() != 1) { + status = MS::kInvalidParameter; + return {}; + } + + return array[0].asChar(); +} + +static MString getStringFlagArg(const MArgParser& argData, const char* flag) +{ + if (!argData.isFlagSet(flag)) + return {}; + + MString value; + argData.getFlagArgument(flag, 0, value); + return value; +} + MStatus USDImportDialogCmd::doIt(const MArgList& args) { MStatus st; @@ -199,28 +235,26 @@ MStatus USDImportDialogCmd::doIt(const MArgList& args) // No command object is expected if (argData.isFlagSet(kApplyToProxyFlag)) { - MStringArray proxyArray; - st = argData.getObjects(proxyArray); - if (!st || proxyArray.length() != 1) - return MS::kInvalidParameter; + MString proxy = getStringArg(argData, st); + if (!st) + return st; - return applyToProxy(proxyArray[0]); + return applyToProxy(proxy); } - MStringArray filenameArray; - st = argData.getObjects(filenameArray); - if (st && (filenameArray.length() > 0)) { + const MString filename = getStringArg(argData, st); + if (st) { // We only use the first one. MFileObject fo; MString assetPath; - fo.setRawFullName(filenameArray[0]); + fo.setRawFullName(filename); bool validTarget = fo.exists(); if (!validTarget) { // Give the default usd-asset-resolver a chance - if (const char* cStr = filenameArray[0].asChar()) { + if (const char* cStr = filename.asChar()) { validTarget = !ArGetResolver().Resolve(cStr).empty(); if (validTarget) - assetPath = filenameArray[0]; + assetPath = filename; } } else assetPath = fo.resolvedFullName(); @@ -229,6 +263,15 @@ MStatus USDImportDialogCmd::doIt(const MArgList& args) USDQtUtil usdQtUtil; ImportData& importData = ImportData::instance(); + if (argData.isFlagSet(kPrimPathFlag)) { + ImportData& importData = ImportData::instance(); + importData.setFilename(filename.asChar()); + // Note: must be done after setting the filename because setting + // the filename clears the data. + const MString primPath = getStringFlagArg(argData, kPrimPathFlag); + importData.setRootPrimPath(primPath.asChar()); + } + // Creating the View can pause Maya, usually only briefly but it's noticable, so we'll // toggle the wait cursor to show that it's working. QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -236,8 +279,16 @@ MStatus USDImportDialogCmd::doIt(const MArgList& args) const MString parentWindowName = parseTextArg(argData, kParentWindowFlag, ""); QWidget* parentWindow = findParentWindow(parentWindowName); - std::unique_ptr usdImportDialog( - new USDImportDialog(assetPath.asChar(), &importData, usdQtUtil, parentWindow)); + USDImportDialogOptions options; + options.title = getStringFlagArg(argData, kWindowTitleFlag).asChar(); + options.helpLabel = getStringFlagArg(argData, kHelpLabelFlag).asChar(); + options.helpURL = getStringFlagArg(argData, kHelpTokenFlag).asChar(); + options.showHeaderMessage = !argData.isFlagSet(kHideRoot); + options.showRoot = !argData.isFlagSet(kHideRoot); + options.showVariants = !argData.isFlagSet(kHideVariantsFlag); + + std::unique_ptr usdImportDialog(new USDImportDialog( + assetPath.asChar(), &importData, options, usdQtUtil, parentWindow)); QApplication::restoreOverrideCursor(); @@ -269,13 +320,19 @@ MSyntax USDImportDialogCmd::createSyntax() MSyntax syntax; syntax.enableQuery(true); syntax.enableEdit(false); - syntax.addFlag(kPrimPathFlag, kPrimPathFlagLong); + syntax.addFlag(kPrimPathFlag, kPrimPathFlagLong, MSyntax::kString); syntax.addFlag(kClearDataFlag, kClearDataFlagLong); syntax.addFlag(kApplyToProxyFlag, kApplyToProxyFlagLong); syntax.addFlag(kPrimCountFlag, kPrimCountFlagLong); syntax.addFlag(kSwitchedVariantCountFlag, kSwitchedVariantCountFlagLong); syntax.addFlag(kParentWindowFlag, kParentWindowFlagLong, MSyntax::kString); + syntax.addFlag(kHideRoot, kHideRootLong); + syntax.addFlag(kHideVariantsFlag, kHideVariantsFlagLong); + syntax.addFlag(kWindowTitleFlag, kWindowTitleFlagLong, MSyntax::kString); + syntax.addFlag(kHelpLabelFlag, kHelpLabelFlagLong, MSyntax::kString); + syntax.addFlag(kHelpTokenFlag, kHelpTokenFlagLong, MSyntax::kString); + syntax.setObjectType(MSyntax::kStringObjects, 0, 1); return syntax; } diff --git a/lib/usd/ui/importDialog/images/defaultPrim_100.png b/lib/usd/ui/importDialog/images/defaultPrim_100.png new file mode 100644 index 0000000000..c6788ef5e4 Binary files /dev/null and b/lib/usd/ui/importDialog/images/defaultPrim_100.png differ diff --git a/lib/usd/ui/importDialog/images/defaultPrim_150.png b/lib/usd/ui/importDialog/images/defaultPrim_150.png new file mode 100644 index 0000000000..e9317a7bd5 Binary files /dev/null and b/lib/usd/ui/importDialog/images/defaultPrim_150.png differ diff --git a/lib/usd/ui/importDialog/images/defaultPrim_200.png b/lib/usd/ui/importDialog/images/defaultPrim_200.png new file mode 100644 index 0000000000..2f78c31156 Binary files /dev/null and b/lib/usd/ui/importDialog/images/defaultPrim_200.png differ diff --git a/lib/usd/ui/importDialog/images/ui.qrc b/lib/usd/ui/importDialog/images/ui.qrc index 68fa235810..e2b478e1db 100644 --- a/lib/usd/ui/importDialog/images/ui.qrc +++ b/lib/usd/ui/importDialog/images/ui.qrc @@ -4,6 +4,7 @@ checkboxOffDisabled_100.png checkboxOn_100.png checkboxOnDisabled_100.png + defaultPrim_100.png checkboxOff_150.png checkboxOff_200.png @@ -13,6 +14,8 @@ checkboxOn_200.png checkboxOnDisabled_150.png checkboxOnDisabled_200.png + defaultPrim_150.png + defaultPrim_200.png diff --git a/lib/usd/ui/importDialogDemo/testMayaUsdUI.cpp b/lib/usd/ui/importDialogDemo/testMayaUsdUI.cpp index 939850dd21..c0f5cc9c38 100644 --- a/lib/usd/ui/importDialogDemo/testMayaUsdUI.cpp +++ b/lib/usd/ui/importDialogDemo/testMayaUsdUI.cpp @@ -101,8 +101,9 @@ int main(int argc, char* argv[]) } // Create and show the ImportUI - TestUIQtUtil uiQtUtil; - MayaUsd::USDImportDialog usdImportDialog(usdFile, &importData, uiQtUtil); + TestUIQtUtil uiQtUtil; + MayaUsd::USDImportDialogOptions options; + MayaUsd::USDImportDialog usdImportDialog(usdFile, &importData, options, uiQtUtil); // Give the dialog the Maya dark style. QStyle* adsk = app.style(); diff --git a/plugin/adsk/scripts/mayaUsd_USDRootFileRelative.py b/plugin/adsk/scripts/mayaUsd_USDRootFileRelative.py index 20eaabbdee..967067d9bc 100644 --- a/plugin/adsk/scripts/mayaUsd_USDRootFileRelative.py +++ b/plugin/adsk/scripts/mayaUsd_USDRootFileRelative.py @@ -219,20 +219,42 @@ def onMakePathRelativeChanged(cls, checked): enableFields = cls._canBeRelative and checked cmds.textFieldGrp(cls.kUnresolvedPathTextField, edit=True, enable=enableFields, text='') if enableFields: - cls.updateFilePathPreviewFields(checked) + cls.updateFilePathPreviewFields(cls.getRawSelectedFile(), checked) @classmethod def onfileNameEditFieldChanged(cls, text): """Callback from Qt textChanged event from fileNameEditField.""" - cls.updateFilePathPreviewFields() + cls.updateFilePathRelatedUi(cls.getRawSelectedFile()) @classmethod def onLookinTextChanged(cls, text): """Callback from Qt currentTextChanged from LookIn combobox.""" - cls.updateFilePathPreviewFields() + cls.updateFilePathRelatedUi(cls.getRawSelectedFile()) @classmethod - def updateFilePathPreviewFields(cls, makePathRelative=None): + def getRawSelectedFile(cls): + """Determine what the currently-selected file full path name.""" + # If the accept button is disabled it means there is no valid input in the file + # name edit field. + selFiles = cls._fileDialog.selectedFiles() if cls._fileDialog and cls._acceptButton and cls._acceptButton.isEnabled() else None + selectedFile = selFiles[0] if selFiles else '' + + if cls._ensureUsdExtension: + # Make sure the file path has a valid USD extension. This is NOT done by the fileDialog so + # the user is free to enter any extension they want. The layer editor code will then verify + # (and fix if needed) the file path before saving. We do the same here for preview. + selectedFile = mayaUsdLib.Util.ensureUSDFileExtension(selectedFile) if selectedFile else '' + + return selectedFile + + @classmethod + def updateFilePathRelatedUi(cls, unresolvedPath): + """Update all UI that cares about the given selected file.""" + cls.updateFilePathPreviewFields(unresolvedPath) + + @classmethod + def updateFilePathPreviewFields(cls, unresolvedPath, makePathRelative=None): + """Update the file-path preview UI.""" if not cls._haveRelativePathFields: return @@ -245,19 +267,6 @@ def updateFilePathPreviewFields(cls, makePathRelative=None): if not makePathRelative: return - # If the accept button is disabled it means there is no valid input in the file - # name edit field. - selFiles = cls._fileDialog.selectedFiles() if cls._fileDialog and cls._acceptButton and cls._acceptButton.isEnabled() else None - selectedFile = selFiles[0] if selFiles else '' - - if cls._ensureUsdExtension: - # Make sure the file path has a valid USD extension. This is NOT done by the fileDialog so - # the user is free to enter any extension they want. The layer editor code will then verify - # (and fix if needed) the file path before saving. We do the same here for preview. - unresolvedPath = mayaUsdLib.Util.ensureUSDFileExtension(selectedFile) if selectedFile else '' - else: - unresolvedPath = selectedFile - relativePath = '' if unresolvedPath and cls._relativeToDir: relativePath = mayaUsdLib.Util.getPathRelativeToDirectory(unresolvedPath, cls._relativeToDir) @@ -269,13 +278,13 @@ def updateFilePathPreviewFields(cls, makePathRelative=None): def selectionChanged(cls, parentLayout, selection): """Callback from fileDialog selectionChanged.""" cmds.setParent(parentLayout) - cls.updateFilePathPreviewFields() + cls.updateFilePathRelatedUi(cls.getRawSelectedFile()) @classmethod def fileTypeChanged(cls, parentLayout, newType): """Callback from fileDialog command fileTypeChanged.""" cmds.setParent(parentLayout) - cls.updateFilePathPreviewFields() + cls.updateFilePathRelatedUi(cls.getRawSelectedFile()) class usdRootFileRelative(usdFileRelative): ''' @@ -430,6 +439,7 @@ def uiCommit(cls, parentLayout, selectedFile=None): compositionArc = values[mayaRefUtils.compositionArcKey] listEditType = values[mayaRefUtils.listEditTypeKey] loadPayload = values[mayaRefUtils.loadPayloadKey] + primPath = values[mayaRefUtils.referencedPrimPathKey] wantReference = bool(compositionArc == mayaRefUtils.compositionArcReference) wantPrepend = bool(listEditType == mayaRefUtils.listEditTypePrepend) @@ -438,6 +448,18 @@ def uiCommit(cls, parentLayout, selectedFile=None): mayaUsdUtils.saveWantReferenceCompositionArc(wantReference) mayaUsdUtils.saveWantPrependCompositionArc(wantPrepend) mayaUsdUtils.saveWantPayloadLoaded(wantLoad) + mayaUsdUtils.saveReferencedPrimPath(primPath) + + @classmethod + def updateFilePathRelatedUi(cls, unresolvedPath): + """Update all UI that cares about the given selected file.""" + super(usdAddRefOrPayloadRelativeToEditTargetLayer, cls).updateFilePathRelatedUi(unresolvedPath) + cls.updateMayaReferenceUi(unresolvedPath) + + @classmethod + def updateMayaReferenceUi(cls, unresolvedPath): + mayaRefUtils.updateUsdRefOrPayloadUI(unresolvedPath) + class usdImageRelativeToEditTargetLayer(usdFileRelativeToEditTargetLayer): '''