1#!/pxrpythonsubst
2#
3# Copyright 2017 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
25# This script is a simple driver for the PCP level of the composition
26# algorithm.  Given the inputs (currently just a root layer), it
27# walks namespace and dumps out the results for every prim.
28
29from __future__ import print_function
30import sys, os, argparse, re
31from pxr import Pcp, Sdf, Work
32
33# Parse options.
34parser = argparse.ArgumentParser()
35parser.add_argument('layer', nargs='+',
36        help = 'A path to a scene description layer.')
37parser.add_argument('--errorFile', dest='errorFileName',
38        help = 'Pcp errors will be outputted into the specified file.')
39parser.add_argument('-d', '--dumpPath', dest='dumpPathStr',
40        default=Sdf.Path.emptyPath,
41        help = 'Print the internal composition structures for the given path.')
42parser.add_argument('--layerStackOnly', action='store_true',
43        dest='layerStackOnly',
44        default=False, help = 'Dump only the layer stack.')
45parser.add_argument('--dumpMaps', action='store_true', dest='dumpMaps',
46        default=False, help = 'Include the MapFunction when dumping a path.')
47parser.add_argument('--variantFallbacks', dest='variantFallbacks', default='',
48        help = 'A dictionary of variant fallbacks.  ' +
49        'The default {"standin":["render"]}.')
50parser.add_argument('--payloads', dest='payloads', default='.*',
51        help = 'A regular expression identifying paths of prims where ' +
52        'payload should be included.  Paths do not include angle brackets. ' +
53        'Default includes every payload.')
54parser.add_argument('--session', dest='session', default='',
55        help = 'The asset path to the session layer.')
56args = parser.parse_args()
57
58# Configure based on the given args.
59if args.variantFallbacks:
60    variantFallbacks = eval(args.variantFallbacks)
61else:
62    variantFallbacks = {'standin':['render']}
63
64payloadRegex = re.compile(args.payloads)
65hadError = False
66errorFile = None
67if args.errorFileName:
68    errorFile = open(args.errorFileName, "w")
69if args.dumpMaps and args.dumpPathStr == '':
70    print('--dumpMaps must be used in tandem with --dumpPath')
71    sys.exit(0)
72dumpPath = Sdf.Path(args.dumpPathStr) if args.dumpPathStr else Sdf.Path()
73
74Work.SetMaximumConcurrencyLimit()
75
76def PrintErrorMessage(errorFile, msg):
77    if errorFile:
78        print(msg, file=errorFile)
79    else:
80        print(msg, file=sys.stderr)
81
82def PrintErrors(errorFile, errors):
83    global hadError
84    hadError = True
85    for err in errors:
86        PrintErrorMessage(errorFile, str(err))
87    PrintErrorMessage(errorFile, '')
88
89
90# Load the session layer, if any.
91sessionLayer = None
92if args.session:
93    print('Loading session @%s@\n' % args.session)
94    sessionLayerPath = args.session
95    assert sessionLayerPath
96    sessionLayer = Sdf.Layer.FindOrOpen(sessionLayerPath)
97    assert sessionLayer
98
99for layerPath in args.layer:
100    print('Loading @%s@\n' % layerPath)
101
102    rootLayerPath = layerPath
103    assert rootLayerPath
104
105    rootLayer = Sdf.Layer.FindOrOpen(rootLayerPath)
106    assert rootLayer
107
108    def GetLayerLabel(layer):
109        # Convert backslashes to forward slashes so comparisons
110        # against baselines work on Windows
111        return (os.path.relpath(layer.realPath,
112                                os.path.dirname(rootLayer.realPath))
113                .replace("\\", "/"))
114
115    def WalkNodes(node):
116        yield node
117        for child in node.children:
118            for descendant in WalkNodes(child):
119                yield descendant
120
121    # Dump the layer stack.
122    errors = []
123    layerStackId = Pcp.LayerStackIdentifier( rootLayer, sessionLayer )
124    pcpCache = Pcp.Cache(layerStackId)
125    assert pcpCache.GetVariantFallbacks() == {}
126    pcpCache.SetVariantFallbacks(variantFallbacks)
127    assert pcpCache.GetVariantFallbacks() == variantFallbacks
128    (layerStackData, errors) = pcpCache.ComputeLayerStack(layerStackId)
129    layerStack = layerStackData.layers
130
131    print('-'*72)
132    print('Layer Stack:')
133    for layer in layerStack:
134        print('    ', GetLayerLabel(layer))
135    print('')
136
137    if len(layerStackData.localErrors) > 0:
138        PrintErrorMessage(errorFile, '-'*72)
139        PrintErrorMessage(errorFile, 'Errors while computing Layer Stack\n')
140        PrintErrors(errorFile, layerStackData.localErrors)
141
142    if args.layerStackOnly:
143        sys.exit(0)
144
145
146    prims = []
147
148    if dumpPath:
149        # Jump straight to the prim at the given path.
150        # First, make sure to load any payloads that might contain it.
151        pcpCache.RequestPayloads(dumpPath.GetPrefixes(), [])
152        prims = [dumpPath]
153    else:
154        (pseudoRootIndex, _) = pcpCache.ComputePrimIndex('/')
155        prims = [Sdf.Path('/').AppendChild(child)
156                 for child in pseudoRootIndex.ComputePrimChildNames()[0]]
157
158    while prims:
159        primPath, prims = prims[0], prims[1:]
160
161        # Check regex for payload inclusion.
162        if payloadRegex.match(str(primPath)):
163            pcpCache.RequestPayloads([primPath], [])
164
165        # Gather composition results for this prim.
166        errors = []
167        assert pcpCache.FindPrimIndex(primPath) is None
168        (primIndex, primIndexErrors) = pcpCache.ComputePrimIndex( primPath )
169        assert pcpCache.FindPrimIndex(primPath) is not None
170        errors += primIndexErrors
171
172        if len(primIndex.primStack) == 0:
173            continue
174
175        print('-'*72)
176        print('Results for composing <%s>' % (primPath))
177
178        # Gather all applied variant selections.
179        vselMap = {}
180        nodesWithOffsets = []
181        for node in WalkNodes(primIndex.rootNode):
182            if node.path.IsPrimVariantSelectionPath():
183                vset, vsel = node.path.GetVariantSelection()
184                if vset not in vselMap:
185                    vselMap[vset] = vsel
186            if (not node.mapToParent.timeOffset.IsIdentity() or
187                any([not o.IsIdentity() for o in node.layerStack.layerOffsets])):
188                nodesWithOffsets.append(node)
189
190        # Gather name child and property names
191        childNames, prohibitedChildNames = primIndex.ComputePrimChildNames()
192        propNames = primIndex.ComputePrimPropertyNames()
193
194        prims = [primPath.AppendChild(child) for child in childNames
195                 if child not in prohibitedChildNames] + prims
196
197        # Optionally dump the index for this path.
198        if primPath == dumpPath:
199            print(primIndex.DumpToString(args.dumpMaps))
200
201        propStackMap = {}
202        targetsMap = {}
203        connectionsMap = {}
204        deletedTargetPathsMap = {}
205
206        properties = [primPath.AppendProperty(child) for child in propNames]
207        while properties:
208            propPath, properties = properties[0], properties[1:]
209
210            assert pcpCache.FindPropertyIndex(propPath) is None
211            (propIndex, propIndexErrors) = \
212                pcpCache.ComputePropertyIndex(propPath)
213            assert pcpCache.FindPropertyIndex(propPath) is not None
214            errors += propIndexErrors
215
216            if len(propIndex.propertyStack) == 0:
217                continue
218
219            propStackMap[propPath] = propIndex.propertyStack
220
221            if isinstance(propIndex.propertyStack[0], Sdf.RelationshipSpec):
222                (targets, deletedPaths, targetErrors) = \
223                    pcpCache.ComputeRelationshipTargetPaths(propPath)
224                errors += targetErrors
225                if len(targets) > 0:
226                    targetsMap[propPath] = targets
227                if deletedPaths:
228                    deletedTargetPathsMap[propPath] = deletedPaths
229            elif isinstance(propIndex.propertyStack[0], Sdf.AttributeSpec):
230                (conns, deletedPaths, connErrors) = \
231                    pcpCache.ComputeAttributeConnectionPaths(propPath)
232                errors += connErrors
233                if len(conns) > 0:
234                    connectionsMap[propPath] = conns
235                if deletedPaths:
236                    deletedTargetPathsMap[propPath] = deletedPaths
237
238        if len(primIndex.primStack) > 0:
239            print('\nPrim Stack:')
240            for primSpec in primIndex.primStack:
241                # Determine a short form of the spec's layer's path.
242                layerLabel = GetLayerLabel(primSpec.layer)
243                print('    %-20s %s' % (layerLabel, primSpec.path))
244
245        if len(nodesWithOffsets) > 0:
246            print('\nTime Offsets:')
247            for node in nodesWithOffsets:
248                print('    %-20s %-15s %-10s (offset=%.2f, scale=%.2f)' % \
249                    (GetLayerLabel(node.layerStack.layers[0]),
250                     node.path, node.arcType.displayName,
251                     node.mapToRoot.timeOffset.offset,
252                     node.mapToRoot.timeOffset.scale))
253
254                for (layer, offset) in zip(node.layerStack.layers,
255                                           node.layerStack.layerOffsets):
256                    if not offset.IsIdentity():
257                        print('        %-32s %-10s (offset=%.2f, ' \
258                            'scale=%.2f)' % \
259                            (GetLayerLabel(layer),
260                             'sublayer',
261                             offset.offset, offset.scale))
262
263        if len(vselMap) > 0:
264            print('\nVariant Selections:')
265            for vsetName in sorted(vselMap.keys()):
266                print('    {%s = %s}' % (vsetName, vselMap[vsetName]))
267
268        if len(childNames) > 0:
269            print('\nChild names:')
270            print('    ', childNames)
271
272        if len(prohibitedChildNames) > 0:
273            print('\nProhibited child names:')
274            # Write the set of prohibited names in stable (sorted) order.
275            print('    ', sorted(prohibitedChildNames))
276
277        if len(propNames) > 0:
278            print('\nProperty names:')
279            print('    ', propNames)
280
281        if len(propStackMap) > 0:
282            print('\nProperty stacks:')
283            for propPath in sorted(propStackMap.keys()):
284                print('%s:' % (propPath))
285                for propSpec in propStackMap[propPath]:
286                    # Determine a short form of the spec's layer's path.
287                    layerLabel = GetLayerLabel(propSpec.layer)
288                    print('    %-20s %s' % (layerLabel, propSpec.path))
289
290        def _PrintTargets(targetMap):
291            for propPath in sorted(targetMap.keys()):
292                print('%s:' % (propPath))
293                for targetPath in targetMap[propPath]:
294                    print('    %s' % targetPath)
295                    # Target paths should never include variant selections.
296                    # Variant selections are part of addressing layer
297                    # opinion storage (like the asset path); they are
298                    # not a feature of composed scene namespace.
299                    assert not targetPath.ContainsPrimVariantSelection(), \
300                        'Target path %s has variant selections' % targetPath
301
302        if len(targetsMap) > 0:
303            print('\nRelationship targets:')
304            _PrintTargets(targetsMap)
305
306        if len(connectionsMap) > 0:
307            print('\nAttribute connections:')
308            _PrintTargets(connectionsMap)
309
310        if deletedTargetPathsMap:
311            print('\nDeleted target paths:')
312            _PrintTargets(deletedTargetPathsMap)
313
314        # Print out errors encountered while composing this prim.
315        if len(errors) > 0:
316            PrintErrorMessage(errorFile, '-'*72)
317            PrintErrorMessage(errorFile,
318                              'Errors while composing <%s>\n' % (primPath))
319            PrintErrors(errorFile, errors)
320
321        print('')
322
323        # Stop after we hit the path to dump.
324        if primPath == dumpPath:
325            sys.exit(0)
326
327if errorFile:
328    errorFile.close()
329
330if hadError and not errorFile:
331    print("ERROR: Unexpected error(s) encountered during test!", file=sys.stderr)
332    sys.exit(1)
333