1#!/pxrpythonsubst
2#
3# Copyright 2018 Pixar
4#
5# Licensed under the Apache License, Version 2.0 (the "Apache License")
6# with the following modification; you may not use this file except in
7# compliance with the Apache License and the following modification to it:
8# Section 6. Trademarks. is deleted and replaced with:
9#
10# 6. Trademarks. This License does not grant permission to use the trade
11#    names, trademarks, service marks, or product names of the Licensor
12#    and its affiliates, except as required to comply with Section 4(c) of
13#    the License and to reproduce the content of the NOTICE file.
14#
15# You may obtain a copy of the Apache License at
16#
17#     http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the Apache License with the above modification is
21# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22# KIND, either express or implied. See the Apache License for the specific
23# language governing permissions and limitations under the Apache License.
24#
25import argparse
26import os
27import sys
28
29from pxr import Usd
30
31
32METADATA_KEYS_TO_SKIP = ('typeName', 'specifier', 'kind', 'active')
33
34
35def _Msg(msg):
36    sys.stdout.write(msg + '\n')
37
38def _Err(msg):
39    sys.stderr.write(msg + '\n')
40
41
42class USDAccessor(object):
43    @staticmethod
44    def GetChildren(prim):
45        return prim.GetAllChildren()
46
47    @staticmethod
48    def GetProperties(prim):
49        return prim.GetAuthoredProperties()
50
51    @staticmethod
52    def GetPropertyName(prop):
53        return prop.GetName()
54
55    @staticmethod
56    def GetMetadata(prim):
57        return prim.GetAllAuthoredMetadata().keys()
58
59    @staticmethod
60    def GetName(prim):
61        return prim.GetName()
62
63    @staticmethod
64    def GetTypeName(prim):
65        return prim.GetTypeName()
66
67    @staticmethod
68    def GetSpecifier(prim):
69        return prim.GetSpecifier()
70
71    @staticmethod
72    def GetKind(prim):
73        return Usd.ModelAPI(prim).GetKind()
74
75    @staticmethod
76    def HasAuthoredActive(prim):
77        return prim.HasAuthoredActive()
78
79    @staticmethod
80    def IsActive(prim):
81        return prim.IsActive()
82
83
84class SdfAccessor(object):
85    @staticmethod
86    def GetChildren(prim):
87        return prim.nameChildren
88
89    @staticmethod
90    def GetProperties(prim):
91        return prim.properties
92
93    @staticmethod
94    def GetPropertyName(prop):
95        return prop.name
96
97    @staticmethod
98    def GetMetadata(prim):
99        return prim.ListInfoKeys()
100
101    @staticmethod
102    def GetName(prim):
103        return prim.name
104
105    @staticmethod
106    def GetTypeName(prim):
107        return prim.typeName
108
109    @staticmethod
110    def GetSpecifier(prim):
111        return prim.specifier
112
113    @staticmethod
114    def GetKind(prim):
115        return prim.kind
116
117    @staticmethod
118    def HasAuthoredActive(prim):
119        return 'active' in prim.ListInfoKeys()
120
121    @staticmethod
122    def IsActive(prim):
123        return prim.active
124
125
126def GetPrimLabel(acc, prim):
127    spec = acc.GetSpecifier(prim).displayName.lower()
128    typeName = acc.GetTypeName(prim)
129    if typeName:
130        definition = '{} {}'.format(spec, typeName)
131    else:
132        definition = spec
133    label = '{} [{}]'.format(acc.GetName(prim), definition)
134
135    shortMetadata = []
136
137    if not acc.IsActive(prim):
138        shortMetadata.append('active = false')
139    elif acc.HasAuthoredActive(prim):
140        shortMetadata.append('active = true')
141
142    kind = acc.GetKind(prim)
143    if kind:
144        shortMetadata.append('kind = {}'.format(kind))
145
146    if shortMetadata:
147        label += ' ({})'.format(', '.join(shortMetadata))
148    return label
149
150
151def PrintPrim(args, acc, prim, prefix, isLast):
152    if not isLast:
153        lastStep = ' |--'
154        if acc.GetChildren(prim):
155            attrStep = ' |   |'
156        else:
157            attrStep = ' |    '
158    else:
159        lastStep = ' `--'
160        if acc.GetChildren(prim):
161            attrStep = '     |'
162        else:
163            attrStep = '      '
164
165    if args.simple:
166        label = acc.GetName(prim)
167    else:
168        label = GetPrimLabel(acc, prim)
169
170    _Msg('{}{}{}'.format(prefix, lastStep, label))
171
172    attrs = []
173    if args.metadata:
174        mdKeys = filter(lambda x: x not in METADATA_KEYS_TO_SKIP, sorted(acc.GetMetadata(prim)))
175        attrs.extend('({})'.format(md) for md in mdKeys)
176
177    if args.attributes:
178        attrs.extend('.{}'.format(acc.GetPropertyName(prop)) for prop in acc.GetProperties(prim))
179
180    numAttrs = len(attrs)
181    for i, attr in enumerate(attrs):
182        if i < numAttrs - 1:
183            _Msg('{}{} :--{}'.format(prefix, attrStep, attr))
184        else:
185            _Msg('{}{} `--{}'.format(prefix, attrStep, attr))
186
187
188def PrintChildren(args, acc, prim, prefix):
189    children = acc.GetChildren(prim)
190    numChildren = len(children)
191    for i, child in enumerate(children):
192        if i < numChildren - 1:
193            PrintPrim(args, acc, child, prefix, isLast=False)
194            PrintChildren(args, acc, child, prefix + ' |  ')
195        else:
196            PrintPrim(args, acc, child, prefix, isLast=True)
197            PrintChildren(args, acc, child, prefix + '    ')
198
199
200def PrintStage(args, stage):
201    _Msg('/')
202    PrintChildren(args, USDAccessor, stage.GetPseudoRoot(), '')
203
204
205def PrintLayer(args, layer):
206    _Msg('/')
207    PrintChildren(args, SdfAccessor, layer.pseudoRoot, '')
208
209
210def PrintTree(args, path):
211    if args.flatten:
212        popMask = (None if args.populationMask is None else Usd.StagePopulationMask())
213        if popMask:
214            for mask in args.populationMask:
215                popMask.Add(mask)
216        if popMask:
217            if args.unloaded:
218                stage = Usd.Stage.OpenMasked(path, popMask, Usd.Stage.LoadNone)
219            else:
220                stage = Usd.Stage.OpenMasked(path, popMask)
221        else:
222            if args.unloaded:
223                stage = Usd.Stage.Open(path, Usd.Stage.LoadNone)
224            else:
225                stage = Usd.Stage.Open(path)
226        PrintStage(args, stage)
227    elif args.flattenLayerStack:
228        from pxr import UsdUtils
229        stage = Usd.Stage.Open(path, Usd.Stage.LoadNone)
230        layer = UsdUtils.FlattenLayerStack(stage)
231        PrintLayer(args, layer)
232    else:
233        from pxr import Sdf
234        layer = Sdf.Layer.FindOrOpen(path)
235        PrintLayer(args, layer)
236
237
238def main():
239    parser = argparse.ArgumentParser(
240        description='''Writes the tree structure of a USD file. The default is to inspect a single USD file.
241Use the --flatten argument to see the flattened (or composed) Stage tree.
242Special metadata "kind" and "active" are always shown if authored unless --simple is provided.''')
243
244    parser.add_argument('inputPath')
245    parser.add_argument(
246        '--unloaded', action='store_true',
247        dest='unloaded',
248        help='Do not load payloads')
249    parser.add_argument(
250        '--attributes', '-a', action='store_true',
251        dest='attributes',
252        help='Display authored attributes')
253    parser.add_argument(
254        '--metadata', '-m', action='store_true',
255        dest='metadata',
256        help='Display authored metadata (active and kind are part of the label and not shown as individual items)')
257    parser.add_argument(
258        '--simple', '-s', action='store_true',
259        dest='simple',
260        help='Only display prim names: no specifier, kind or active state.')
261    parser.add_argument(
262        '--flatten', '-f', action='store_true', help='Compose the stage with the '
263        'input file as root layer and write the flattened content.')
264    parser.add_argument(
265        '--flattenLayerStack', action='store_true',
266        help='Flatten the layer stack with the given root layer. '
267        'Unlike --flatten, this does not flatten composition arcs (such as references).')
268    parser.add_argument('--mask', action='store',
269                        dest='populationMask',
270                        metavar='PRIMPATH[,PRIMPATH...]',
271                        help='Limit stage population to these prims, '
272                        'their descendants and ancestors.  To specify '
273                        'multiple paths, either use commas with no spaces '
274                        'or quote the argument and separate paths by '
275                        'commas and/or spaces.  Requires --flatten.')
276
277    args = parser.parse_args()
278
279    # split args.populationMask into paths.
280    if args.populationMask:
281        if not args.flatten:
282            # You can only mask a stage, not a layer.
283            _Err("%s: error: --mask requires --flatten" % parser.prog)
284            return 1
285        args.populationMask = args.populationMask.replace(',', ' ').split()
286
287    from pxr import Ar
288    resolver = Ar.GetResolver()
289
290    try:
291        if hasattr(Ar.Resolver, "ConfigureResolverForAsset"):
292            resolver.ConfigureResolverForAsset(args.inputPath)
293        resolverContext = resolver.CreateDefaultContextForAsset(args.inputPath)
294        with Ar.ResolverContextBinder(resolverContext):
295            resolved = resolver.Resolve(args.inputPath)
296            if not resolved:
297                _Err('Cannot resolve inputPath %r'%resolved)
298                return 1
299            PrintTree(args, resolved)
300    except Exception as e:
301        _Err("Failed to process '%s' - %s" % (args.inputPath, e))
302        return 1
303
304    return 0
305
306
307if __name__ == "__main__":
308    # Restore signal handling defaults to allow output redirection and the like.
309    import platform
310    if platform.system() != 'Windows':
311        import signal
312        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
313    sys.exit(main())
314