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