Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open usdview without specifying a file #3321

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pxr/usdImaging/usdviewq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,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):
'''
Expand Down Expand Up @@ -344,11 +345,16 @@ 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 is None (Falsy value), a default context will be created.
"""
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.
if hasattr(r, "ConfigureResolverForAsset"):
Expand Down
53 changes: 39 additions & 14 deletions pxr/usdImaging/usdviewq/appController.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,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)

Expand Down Expand Up @@ -1200,22 +1200,30 @@ def _MuteMatchingLayers():
# second pass in order to mute additional
# layers populated after loading
_MuteMatchingLayers()

def _openStage(self, usdFilePath, sessionFilePath,
populationMaskPaths, muteLayersRe):

def _getEmptyStage(self):

with self._makeTimer('create empty stage'):
sessionLayer = Sdf.Layer.CreateAnonymous()
stage = Usd.Stage.CreateInMemory('Empty Stage', sessionLayer, self._resolverContextFn(None))

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):
err = ("Error: Unable to open stage '{0}'\n".format(usdFilePath))
if reasons:
err += "\n".join(reasons) + "\n"
return err

if not Ar.GetResolver().Resolve(usdFilePath):
sys.stderr.write(_GetFormattedError(["File not found"]))
sys.exit(1)

if self._mallocTags != 'none':
Tf.MallocTag.Initialize()

with self._makeTimer('open stage "%s"' % usdFilePath):
loadSet = Usd.Stage.LoadNone if (self._unloaded or muteLayersRe) \
Expand Down Expand Up @@ -1262,8 +1270,22 @@ def _GetFormattedError(reasons=None):

if not stage:
sys.stderr.write(_GetFormattedError())
return stage

def _openStage(self, usdFilePath, sessionFilePath,
populationMaskPaths, muteLayersRe):

if self._mallocTags != 'none':
Tf.MallocTag.Initialize()

if not usdFilePath:
stage = self._getEmptyStage()

else:
stage.SetEditTarget(stage.GetSessionLayer())
stage = self._getStageForFile(usdFilePath, sessionFilePath,
populationMaskPaths, muteLayersRe)

stage.SetEditTarget(stage.GetSessionLayer())

if self._mallocTags == 'stage':
DumpMallocTags(stage, "stage-loading")
Expand Down Expand Up @@ -2804,9 +2826,13 @@ def _getSaveFileName(self, caption, recommendedFilename):

return saveName

def _getRecommendedFilenamePrefix(self):
return (self._parserData.usdFile.rsplit('.', 1)[0]
if self._parserData.usdFile
else 'new_file')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be something unique, like when we are creating temp files? It's probably unlikely, but if a bunch of "file-less" usdview's are launched and you do live-editing and want to save out the overrides from more than one, if you go with the recommendation you'll stomp your results. OTOH, you will see that the suggestion is the same for both, and have the ability to change it. Just a thought to consider.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... if you do go that route, given that there's multiple uses of this recommended name, and only one "empty/new stage" per usdview, Seems like we would want just one unique name per-process, right?

Copy link
Author

@MishDivyAmzn MishDivyAmzn Sep 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! The unique names usually look a bit ugly to people, so I wasn't sure about it :). I can replace this function with the following if people think this would be better.

def _getRecommendedFilenamePrefix(self):
    return (self._parserData.usdFile.rsplit('.', 1)[0]
            if self._parserData.usdFile
            else f'new_file_{os.getpid()}_{datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:18]}')

The above function would make a unique name suggestion based on process ID and current datetime (millisecond precision), so almost unique everytime. But could be a bit jarring to look at. Open to suggestions :)

Here's an example of a potential suggestion from the above function:
new_file_12345_20240927_154510_12

We can potentially remove the datetime portion if we just need a unique name per process ID.

Thanks for the feedback, lmk if anything else stands out.


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)
Expand All @@ -2830,7 +2856,7 @@ def _saveOverridesAs(self):
# 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
# export it to the given filename. If it isn't anonymous (i.e., it
# is a regular usd file on disk), export the session layer and add
# the stage root file as a sublayer.
rootLayer = self._dataModel.stage.GetRootLayer()
Expand Down Expand Up @@ -2858,8 +2884,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)
Expand All @@ -2874,7 +2899,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()))
Expand Down