Skip to content

Commit dfbc03f

Browse files
MishDivyAmznpixar-oss
authored andcommitted
Allow usdview to open without specifying a file
Closes #3321 (Internal change: 2384347)
1 parent 7918f50 commit dfbc03f

File tree

5 files changed

+81
-26
lines changed

5 files changed

+81
-26
lines changed

pxr/usdImaging/bin/testusdview/CMakeLists.txt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ pxr_install_test_dir(
8383
DEST testUsdviewFileArguments7
8484
)
8585

86+
pxr_install_test_dir(
87+
SRC testenv/testUsdviewWrapper
88+
DEST testUsdviewWrapper0
89+
)
90+
8691
pxr_install_test_dir(
8792
SRC testenv/testUsdviewWrapper
8893
DEST testUsdviewWrapper1
@@ -489,10 +494,22 @@ pxr_register_test(testUsdviewFileArguments7
489494
PXR_USDVIEW_SUPPRESS_STATE_SAVING=1
490495
)
491496

492-
pxr_register_test(testUsdviewWrapper1
497+
pxr_register_test(testUsdviewWrapper0
493498
PYTHON
494499
COMMAND "${CMAKE_INSTALL_PREFIX}/bin/testusdview --testScript testCallback.py"
495-
EXPECTED_RETURN_CODE 2
500+
STDOUT_REDIRECT valid_output_no_file
501+
DIFF_COMPARE valid_output_no_file
502+
EXPECTED_RETURN_CODE 0
503+
ENV
504+
PXR_USDVIEW_SUPPRESS_STATE_SAVING=1
505+
)
506+
507+
pxr_register_test(testUsdviewWrapper1
508+
PYTHON
509+
COMMAND "${CMAKE_INSTALL_PREFIX}/bin/testusdview --testScript testCallback.py test.usda"
510+
STDOUT_REDIRECT valid_output
511+
DIFF_COMPARE valid_output
512+
EXPECTED_RETURN_CODE 0
496513
ENV
497514
PXR_USDVIEW_SUPPRESS_STATE_SAVING=1
498515
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
callback

pxr/usdImaging/usdviewq/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ def RegisterPositionals(self, parser):
111111
register positional arguments on the ArgParser
112112
'''
113113
parser.add_argument('usdFile', action='store',
114+
nargs='?',
114115
type=str,
115-
help='The file to view')
116+
help='The file to view (Optional)')
116117

117118
def RegisterOptions(self, parser):
118119
'''
@@ -372,10 +373,15 @@ def GetResolverContext(self, usdFile):
372373
context is provided. For usdview, configuring an asset context by
373374
default is reasonable, and allows clients that embed usdview to
374375
achieve different behavior when needed.
376+
377+
If usdFile path is not provided, it returns default context.
375378
"""
376379
from pxr import Ar
377380

378381
r = Ar.GetResolver()
382+
383+
if not usdFile:
384+
return r.CreateDefaultContext()
379385

380386
# ConfigureResolverForAsset no longer exists under Ar 2.0; this
381387
# is here for backwards compatibility with Ar 1.0.

pxr/usdImaging/usdviewq/appController.py

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def __init__(self, parserData, resolverContextFn):
403403
self._ui = Ui_MainWindow()
404404
self._ui.setupUi(self._mainWindow)
405405

406-
self._mainWindow.setWindowTitle(parserData.usdFile)
406+
self._mainWindow.setWindowTitle(parserData.usdFile or "New Stage")
407407
self._statusBar = QtWidgets.QStatusBar(self._mainWindow)
408408
self._mainWindow.setStatusBar(self._statusBar)
409409

@@ -1203,17 +1203,22 @@ def _MuteMatchingLayers():
12031203
# layers populated after loading
12041204
_MuteMatchingLayers()
12051205

1206-
def _openStage(self, usdFilePath, sessionFilePath,
1207-
populationMaskPaths, muteLayersRe):
1206+
def _openEmptyStage(self):
1207+
stage = Usd.Stage.CreateInMemory()
1208+
1209+
if not stage:
1210+
sys.stderr.write('Error: Unable to create empty stage\n')
1211+
1212+
return stage
1213+
1214+
def _openStageForFile(self, usdFilePath, sessionFilePath,
1215+
populationMaskPaths, muteLayersRe):
12081216

12091217
def _GetFormattedError(reasons=None):
12101218
err = ("Error: Unable to open stage '{0}'\n".format(usdFilePath))
12111219
if reasons:
12121220
err += "\n".join(reasons) + "\n"
12131221
return err
1214-
1215-
if self._mallocTags != 'none':
1216-
Tf.MallocTag.Initialize()
12171222

12181223
# Pull on the asset resolver here so that the "open stage" time does
12191224
# not include its initialization time for consistency with previous
@@ -1276,6 +1281,21 @@ def _GetFormattedError(reasons=None):
12761281
else:
12771282
stage.SetEditTarget(stage.GetSessionLayer())
12781283

1284+
return stage
1285+
1286+
def _openStage(self, usdFilePath, sessionFilePath,
1287+
populationMaskPaths, muteLayersRe):
1288+
1289+
if self._mallocTags != 'none':
1290+
Tf._mallocTags.Initialize()
1291+
1292+
if not usdFilePath:
1293+
stage = self._openEmptyStage()
1294+
1295+
else:
1296+
stage = self._openStageForFile(usdFilePath, sessionFilePath,
1297+
populationMaskPaths, muteLayersRe)
1298+
12791299
if self._mallocTags == 'stage':
12801300
DumpMallocTags(stage, "stage-loading")
12811301

@@ -2795,6 +2815,12 @@ def _cleanAndClose(self):
27952815

27962816
# Start timer to measure Qt shutdown time
27972817
self._startQtShutdownTimer()
2818+
2819+
2820+
def _getRecommendedFilenamePrefix(self):
2821+
return (self._parserData.usdFile.rsplit('.', 1)[0]
2822+
if self._parserData.usdFile
2823+
else 'new_file')
27982824

27992825
def _openFile(self):
28002826
extensions = Sdf.FileFormat.FindAllFileFormatExtensions()
@@ -2836,14 +2862,17 @@ def _getSaveFileName(self, caption, recommendedFilename):
28362862
return saveName
28372863

28382864
def _saveOverridesAs(self):
2839-
recommendedFilename = self._parserData.usdFile.rsplit('.', 1)[0]
2840-
recommendedFilename += '_overrides.usd'
2865+
recommendedFilename = self._getRecommendedFilenamePrefix()
2866+
if self._parserData.usdFile:
2867+
recommendedFilename += "_overrides.usd"
2868+
else:
2869+
recommendedFilename += ".usd"
28412870

28422871
saveName = self._getSaveFileName(
28432872
'Save Overrides As', recommendedFilename)
28442873
if len(saveName) == 0:
28452874
return
2846-
elif (os.path.isfile(saveName) and
2875+
elif (os.path.isfile(saveName) and self._parserData.usdFile and
28472876
os.path.samefile(saveName, self._parserData.usdFile)):
28482877
msg = QtWidgets.QMessageBox()
28492878
msg.setIcon(QtWidgets.QMessageBox.Critical)
@@ -2857,13 +2886,13 @@ def _saveOverridesAs(self):
28572886
return
28582887

28592888
with BusyContext():
2860-
# In the future, we may allow usdview to be brought up with no file,
2861-
# in which case it would create an in-memory root layer, to which
2862-
# all edits will be targeted. In order to future proof
2863-
# this, first fetch the root layer, and if it is anonymous, just
2864-
# export it to the given filename. If it isn't anonmyous (i.e., it
2865-
# is a regular usd file on disk), export the session layer and add
2866-
# the stage root file as a sublayer.
2889+
# usdview can be brought up with no file, in which case it
2890+
# creates an in-memory root layer, to which all edits are
2891+
# targeted. This first fetches the root layer, and if it is
2892+
# anonymous, just exports it to the given filename. If it
2893+
# isn't anonymous (i.e., it is a regular usd file on disk),
2894+
# exports the session layer and add the stage root file
2895+
# as a sublayer.
28672896
rootLayer = self._dataModel.stage.GetRootLayer()
28682897
if not rootLayer.anonymous:
28692898
self._dataModel.stage.GetSessionLayer().Export(
@@ -2889,7 +2918,7 @@ def _saveOverridesAs(self):
28892918
saveName, 'Created by UsdView')
28902919

28912920
def _saveFlattenedAs(self):
2892-
recommendedFilename = self._parserData.usdFile.rsplit('.', 1)[0]
2921+
recommendedFilename = self._getRecommendedFilenamePrefix()
28932922
recommendedFilename += '_flattened.usd'
28942923

28952924
saveName = self._getSaveFileName(
@@ -2905,7 +2934,7 @@ def _copyViewerImage(self):
29052934

29062935
def _saveViewerImage(self):
29072936
recommendedFilename = "{}_{}{:04d}.png".format(
2908-
self._parserData.usdFile.rsplit('.', 1)[0],
2937+
self._getRecommendedFilenamePrefix(),
29092938
"" if not self.getActiveCamera()
29102939
else self.getActiveCamera().GetName() + "_",
29112940
int(self._dataModel.currentFrame.GetValue()))
@@ -5518,4 +5547,4 @@ def setActiveRenderPassPrim(self, prim):
55185547

55195548
if self._stageView:
55205549
self._stageView.SetActiveRenderPassPrim(prim)
5521-
self._stageView.updateView()
5550+
self._stageView.updateView()

pxr/usdImaging/usdviewq/testenv/testUsdviewqLauncher.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#
88

99
import unittest
10-
import sys
1110

1211
from pxr.Usdviewq import Launcher, InvalidUsdviewOption
1312

@@ -25,15 +24,13 @@ def Run(self, args=None):
2524
"""Run the launcher, and optionally provide a list of command line
2625
arguments. By default, no arguments are used.
2726
"""
28-
args = args or []
29-
self._args = [sys.argv[0]] + args
27+
self._args = args or []
3028
super(ValidationOnlyLauncher, self).Run()
3129

3230
def ParseOptions(self, parser):
3331
"""Parse the given command line arguments rather than the test's command
3432
line arguments.
3533
"""
36-
3734
return parser.parse_args(self._args)
3835

3936
def LaunchPreamble(self, arg_parse_result):
@@ -54,6 +51,11 @@ def test_noOptions(self):
5451

5552
ValidationOnlyLauncher().Run()
5653

54+
def test_usdFileOnly(self):
55+
"""Test the launcher with no arguments except for a usd file."""
56+
57+
ValidationOnlyLauncher().Run(["foo.usda"])
58+
5759
def test_cameraFlag(self):
5860
"""Only a valid non-root prim path or a camera name should be
5961
acceptable.

0 commit comments

Comments
 (0)