diff --git a/pxr/usdImaging/usdviewq/__init__.py b/pxr/usdImaging/usdviewq/__init__.py index 08f9cf6d72..326ee1b569 100644 --- a/pxr/usdImaging/usdviewq/__init__.py +++ b/pxr/usdImaging/usdviewq/__init__.py @@ -111,8 +111,9 @@ def RegisterPositionals(self, parser): register positional arguments on the ArgParser ''' parser.add_argument('usdFile', action='store', + nargs='?', type=str, - help='The file to view') + help='The file to view (Optional)') def RegisterOptions(self, parser): ''' @@ -372,10 +373,15 @@ def GetResolverContext(self, usdFile): context is provided. For usdview, configuring an asset context by default is reasonable, and allows clients that embed usdview to achieve different behavior when needed. + + If usdFile path is not provided, it returns default context. """ from pxr import Ar r = Ar.GetResolver() + + if not usdFile: + return r.CreateDefaultContext() # ConfigureResolverForAsset no longer exists under Ar 2.0; this # is here for backwards compatibility with Ar 1.0. diff --git a/pxr/usdImaging/usdviewq/appController.py b/pxr/usdImaging/usdviewq/appController.py index 1f3d6f748b..f379414805 100644 --- a/pxr/usdImaging/usdviewq/appController.py +++ b/pxr/usdImaging/usdviewq/appController.py @@ -403,7 +403,7 @@ def __init__(self, parserData, resolverContextFn): self._ui = Ui_MainWindow() self._ui.setupUi(self._mainWindow) - self._mainWindow.setWindowTitle(parserData.usdFile) + self._mainWindow.setWindowTitle(parserData.usdFile or "Empty Stage") self._statusBar = QtWidgets.QStatusBar(self._mainWindow) self._mainWindow.setStatusBar(self._statusBar) @@ -1203,7 +1203,16 @@ def _MuteMatchingLayers(): # layers populated after loading _MuteMatchingLayers() - def _openStage(self, usdFilePath, sessionFilePath, + def _getEmptyStage(self): + + with self._makeTimer('create empty stage'): + stage = Usd.Stage.CreateInMemory() + + if not stage: + sys.stderr.write('Error: Unable to create empty stage\n') + return stage + + def _getStageForFile(self, usdFilePath, sessionFilePath, populationMaskPaths, muteLayersRe): def _GetFormattedError(reasons=None): @@ -1281,6 +1290,25 @@ def _GetFormattedError(reasons=None): return stage + def _openStage(self, usdFilePath, sessionFilePath, + populationMaskPaths, muteLayersRe): + + if self._mallocTags != 'none': + Tf.MallocTag.Initialize() + + if not usdFilePath: + stage = self._getEmptyStage() + + else: + stage = self._getStageForFile(usdFilePath, sessionFilePath, + populationMaskPaths, muteLayersRe) + stage.SetEditTarget(stage.GetSessionLayer()) + + if self._mallocTags == 'stage': + DumpMallocTags(stage, "stage-loading") + + return stage + def _closeStage(self): # Close the USD stage. if self._stageView: @@ -2795,6 +2823,12 @@ def _cleanAndClose(self): # Start timer to measure Qt shutdown time self._startQtShutdownTimer() + + + def _getRecommendedFilenamePrefix(self): + return (self._parserData.usdFile.rsplit('.', 1)[0] + if self._parserData.usdFile + else 'new_file') def _openFile(self): extensions = Sdf.FileFormat.FindAllFileFormatExtensions() @@ -2836,14 +2870,13 @@ def _getSaveFileName(self, caption, recommendedFilename): return saveName def _saveOverridesAs(self): - recommendedFilename = self._parserData.usdFile.rsplit('.', 1)[0] - recommendedFilename += '_overrides.usd' + recommendedFilename = self._getRecommendedFilenamePrefix() + '_overrides.usd' saveName = self._getSaveFileName( 'Save Overrides As', recommendedFilename) if len(saveName) == 0: return - elif (os.path.isfile(saveName) and + elif (os.path.isfile(saveName) and self._parserData.usdFile and os.path.samefile(saveName, self._parserData.usdFile)): msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Critical) @@ -2857,13 +2890,13 @@ def _saveOverridesAs(self): return with BusyContext(): - # In the future, we may allow usdview to be brought up with no file, - # in which case it would create an in-memory root layer, to which - # all edits will be targeted. In order to future proof - # this, first fetch the root layer, and if it is anonymous, just - # export it to the given filename. If it isn't anonmyous (i.e., it - # is a regular usd file on disk), export the session layer and add - # the stage root file as a sublayer. + # usdview can be brought up with no file, in which case it + # creates an in-memory root layer, to which all edits are + # targeted. This first fetches the root layer, and if it is + # anonymous, just exports it to the given filename. If it + # isn't anonymous (i.e., it is a regular usd file on disk), + # exports the session layer and add the stage root file + # as a sublayer. rootLayer = self._dataModel.stage.GetRootLayer() if not rootLayer.anonymous: self._dataModel.stage.GetSessionLayer().Export( @@ -2889,8 +2922,7 @@ def _saveOverridesAs(self): saveName, 'Created by UsdView') def _saveFlattenedAs(self): - recommendedFilename = self._parserData.usdFile.rsplit('.', 1)[0] - recommendedFilename += '_flattened.usd' + recommendedFilename = self._getRecommendedFilenamePrefix() + '_flattened.usd' saveName = self._getSaveFileName( 'Save Flattened As', recommendedFilename) @@ -2905,7 +2937,7 @@ def _copyViewerImage(self): def _saveViewerImage(self): recommendedFilename = "{}_{}{:04d}.png".format( - self._parserData.usdFile.rsplit('.', 1)[0], + self._getRecommendedFilenamePrefix(), "" if not self.getActiveCamera() else self.getActiveCamera().GetName() + "_", int(self._dataModel.currentFrame.GetValue()))