-
Notifications
You must be signed in to change notification settings - Fork 127
/
usdlib.py
361 lines (274 loc) · 10.9 KB
/
usdlib.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import os
import re
import logging
try:
from pxr import Usd, UsdGeom, Sdf, Kind
except ImportError:
# Allow to fall back on Multiverse 6.3.0+ pxr usd library
from mvpxr import Usd, UsdGeom, Sdf, Kind
from openpype.client import get_project, get_asset_by_name
from openpype.pipeline import Anatomy, get_current_project_name
log = logging.getLogger(__name__)
# The predefined steps order used for bootstrapping USD Shots and Assets.
# These are ordered in order from strongest to weakest opinions, like in USD.
PIPELINE = {
"shot": [
"usdLighting",
"usdFx",
"usdSimulation",
"usdAnimation",
"usdLayout",
],
"asset": ["usdShade", "usdModel"],
}
def create_asset(
filepath, asset_name, reference_layers, kind=Kind.Tokens.component
):
"""
Creates an asset file that consists of a top level layer and sublayers for
shading and geometry.
Args:
filepath (str): Filepath where the asset.usd file will be saved.
reference_layers (list): USD Files to reference in the asset.
Note that the bottom layer (first file, like a model) would
be last in the list. The strongest layer will be the first
index.
asset_name (str): The name for the Asset identifier and default prim.
kind (pxr.Kind): A USD Kind for the root asset.
"""
# Also see create_asset.py in PixarAnimationStudios/USD endToEnd example
log.info("Creating asset at %s", filepath)
# Make the layer ascii - good for readability, plus the file is small
root_layer = Sdf.Layer.CreateNew(filepath, args={"format": "usda"})
stage = Usd.Stage.Open(root_layer)
# Define a prim for the asset and make it the default for the stage.
asset_prim = UsdGeom.Xform.Define(stage, "/%s" % asset_name).GetPrim()
stage.SetDefaultPrim(asset_prim)
# Let viewing applications know how to orient a free camera properly
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y)
# Usually we will "loft up" the kind authored into the exported geometry
# layer rather than re-stamping here; we'll leave that for a later
# tutorial, and just be explicit here.
model = Usd.ModelAPI(asset_prim)
if kind:
model.SetKind(kind)
model.SetAssetName(asset_name)
model.SetAssetIdentifier("%s/%s.usd" % (asset_name, asset_name))
# Add references to the asset prim
references = asset_prim.GetReferences()
for reference_filepath in reference_layers:
references.AddReference(reference_filepath)
stage.GetRootLayer().Save()
def create_shot(filepath, layers, create_layers=False):
"""Create a shot with separate layers for departments.
Args:
filepath (str): Filepath where the asset.usd file will be saved.
layers (str): When provided this will be added verbatim in the
subLayerPaths layers. When the provided layer paths do not exist
they are generated using Sdf.Layer.CreateNew
create_layers (bool): Whether to create the stub layers on disk if
they do not exist yet.
Returns:
str: The saved shot file path
"""
# Also see create_shot.py in PixarAnimationStudios/USD endToEnd example
stage = Usd.Stage.CreateNew(filepath)
log.info("Creating shot at %s" % filepath)
for layer_path in layers:
if create_layers and not os.path.exists(layer_path):
# We use the Sdf API here to quickly create layers. Also, we're
# using it as a way to author the subLayerPaths as there is no
# way to do that directly in the Usd API.
layer_folder = os.path.dirname(layer_path)
if not os.path.exists(layer_folder):
os.makedirs(layer_folder)
Sdf.Layer.CreateNew(layer_path)
stage.GetRootLayer().subLayerPaths.append(layer_path)
# Lets viewing applications know how to orient a free camera properly
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y)
stage.GetRootLayer().Save()
return filepath
def create_model(filename, asset, variant_subsets):
"""Create a USD Model file.
For each of the variation paths it will payload the path and set its
relevant variation name.
"""
project_name = get_current_project_name()
asset_doc = get_asset_by_name(project_name, asset)
assert asset_doc, "Asset not found: %s" % asset
variants = []
for subset in variant_subsets:
prefix = "usdModel"
if subset.startswith(prefix):
# Strip off `usdModel_`
variant = subset[len(prefix):]
else:
raise ValueError(
"Model subsets must start " "with usdModel: %s" % subset
)
path = get_usd_master_path(
asset=asset_doc, subset=subset, representation="usd"
)
variants.append((variant, path))
stage = _create_variants_file(
filename,
variants=variants,
variantset="model",
variant_prim="/root",
reference_prim="/root/geo",
as_payload=True,
)
UsdGeom.SetStageMetersPerUnit(stage, 1)
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y)
# modelAPI = Usd.ModelAPI(root_prim)
# modelAPI.SetKind(Kind.Tokens.component)
# See http://openusd.org/docs/api/class_usd_model_a_p_i.html#details
# for more on assetInfo
# modelAPI.SetAssetName(asset)
# modelAPI.SetAssetIdentifier(asset)
stage.GetRootLayer().Save()
def create_shade(filename, asset, variant_subsets):
"""Create a master USD shade file for an asset.
For each available model variation this should generate a reference
to a `usdShade_{modelVariant}` subset.
"""
project_name = get_current_project_name()
asset_doc = get_asset_by_name(project_name, asset)
assert asset_doc, "Asset not found: %s" % asset
variants = []
for subset in variant_subsets:
prefix = "usdModel"
if subset.startswith(prefix):
# Strip off `usdModel_`
variant = subset[len(prefix):]
else:
raise ValueError(
"Model subsets must start " "with usdModel: %s" % subset
)
shade_subset = re.sub("^usdModel", "usdShade", subset)
path = get_usd_master_path(
asset=asset_doc, subset=shade_subset, representation="usd"
)
variants.append((variant, path))
stage = _create_variants_file(
filename, variants=variants, variantset="model", variant_prim="/root"
)
stage.GetRootLayer().Save()
def create_shade_variation(filename, asset, model_variant, shade_variants):
"""Create the master Shade file for a specific model variant.
This should reference all shade variants for the specific model variant.
"""
project_name = get_current_project_name()
asset_doc = get_asset_by_name(project_name, asset)
assert asset_doc, "Asset not found: %s" % asset
variants = []
for variant in shade_variants:
subset = "usdShade_{model}_{shade}".format(
model=model_variant, shade=variant
)
path = get_usd_master_path(
asset=asset_doc, subset=subset, representation="usd"
)
variants.append((variant, path))
stage = _create_variants_file(
filename, variants=variants, variantset="shade", variant_prim="/root"
)
stage.GetRootLayer().Save()
def _create_variants_file(
filename,
variants,
variantset,
default_variant=None,
variant_prim="/root",
reference_prim=None,
set_default_variant=True,
as_payload=False,
skip_variant_on_single_file=True,
):
root_layer = Sdf.Layer.CreateNew(filename, args={"format": "usda"})
stage = Usd.Stage.Open(root_layer)
root_prim = stage.DefinePrim(variant_prim)
stage.SetDefaultPrim(root_prim)
def _reference(path):
"""Reference/Payload path depending on function arguments"""
if reference_prim:
prim = stage.DefinePrim(reference_prim)
else:
prim = root_prim
if as_payload:
# Payload
prim.GetPayloads().AddPayload(Sdf.Payload(path))
else:
# Reference
prim.GetReferences().AddReference(Sdf.Reference(path))
assert variants, "Must have variants, got: %s" % variants
log.info(filename)
if skip_variant_on_single_file and len(variants) == 1:
# Reference directly, no variants
variant_path = variants[0][1]
_reference(variant_path)
log.info("Non-variants..")
log.info("Path: %s" % variant_path)
else:
# Variants
append = Usd.ListPositionBackOfAppendList
variant_set = root_prim.GetVariantSets().AddVariantSet(
variantset, append
)
for variant, variant_path in variants:
if default_variant is None:
default_variant = variant
variant_set.AddVariant(variant, append)
variant_set.SetVariantSelection(variant)
with variant_set.GetVariantEditContext():
_reference(variant_path)
log.info("Variants..")
log.info("Variant: %s" % variant)
log.info("Path: %s" % variant_path)
if set_default_variant:
variant_set.SetVariantSelection(default_variant)
return stage
def get_usd_master_path(asset, subset, representation):
"""Get the filepath for a .usd file of a subset.
This will return the path to an unversioned master file generated by
`usd_master_file.py`.
"""
project_name = get_current_project_name()
anatomy = Anatomy(project_name)
project_doc = get_project(
project_name,
fields=["name", "data.code"]
)
if isinstance(asset, dict) and "name" in asset:
# Allow explicitly passing asset document
asset_doc = asset
else:
asset_doc = get_asset_by_name(project_name, asset, fields=["name"])
template_obj = anatomy.templates_obj["publish"]["path"]
path = template_obj.format_strict(
{
"project": {
"name": project_name,
"code": project_doc.get("data", {}).get("code")
},
"folder": {
"name": asset_doc["name"],
},
"asset": asset_doc["name"],
"subset": subset,
"representation": representation,
"version": 0, # stub version zero
}
)
# Remove the version folder
subset_folder = os.path.dirname(os.path.dirname(path))
master_folder = os.path.join(subset_folder, "master")
fname = "{0}.{1}".format(subset, representation)
return os.path.join(master_folder, fname).replace("\\", "/")
def parse_avalon_uri(uri):
# URI Pattern: avalon://{asset}/{subset}.{ext}
pattern = r"avalon://(?P<asset>[^/.]*)/(?P<subset>[^/]*)\.(?P<ext>.*)"
if uri.startswith("avalon://"):
match = re.match(pattern, uri)
if match:
return match.groupdict()