Skip to content

Commit 3662478

Browse files
semagnummatthewlow-dwa
authored andcommitted
Utility script to download and install asset packages
Signed-off-by: Spencer Magnusson <spencer.magnusson@dreamworks.com>
1 parent a27e385 commit 3662478

2 files changed

Lines changed: 266 additions & 0 deletions

File tree

docs/src/pages/main.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ As the complete data set is heavy and different use cases may consume distinct s
6565

6666
*If in doubt, start only with the `Asset Structure` and `Techvar Assets` packages.*
6767

68+
You can use the `install_optional_packages.py` script provided with the ALab repository to automate downloading and/or installing these packages:
69+
70+
```bash
71+
# install all packages in current directory (will take a while)
72+
python install_optional_packages.py --all
73+
# you can provide a .zip filepath for each package if already downloaded, otherwise the script will download each for you
74+
python install_optional_packages.py --techvar --baked_procedurals --texture_pack texture_pack_download.zip --cameras --output ./repos/ALab/
75+
```
76+
6877
## Asset Structure
6978
- Main USD asset structure. No geometry, shaders or lights.
7079

install_optional_packages.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
from contextlib import contextmanager
5+
import logging
6+
import os
7+
import tempfile
8+
import urllib.request
9+
import urllib.error
10+
from zipfile import ZipFile
11+
12+
13+
logger = logging.getLogger(__name__)
14+
15+
## CONSTANTS
16+
# You can also manually download via `curl -L -O <url>`
17+
# and provide the path to the zip file.
18+
19+
TECHVAR_ZIP_URL = 'https://dpel-assets.aswf.io/usd-alab/alab-techvars.v2.2.0.zip'
20+
BAKED_PROCEDURALS_ZIP_URL = 'https://dpel-assets.aswf.io/usd-alab/alab-procedurals.v2.2.0.zip'
21+
TEXTURE_PACK_ZIP_URL = 'https://aswf-dpel-assets.s3.amazonaws.com/usd-alab/alab-textures.v2.2.0.zip'
22+
CAMERAS_ZIP_URL = 'https://dpel-assets.aswf.io/usd-alab/alab-cameras.v2.2.0.zip'
23+
24+
25+
## UTILITY FUNCTIONS
26+
27+
28+
@contextmanager
29+
def _get_or_download(url, description, zip_file_path=None):
30+
"""Utility function to either use provided zip file path or download from URL.
31+
32+
Args:
33+
url (str): URL to download from if zip_file_path is None or empty string.
34+
description (str): Description of the file being downloaded, for logging.
35+
zip_file_path (str or None): If provided, path to zip file. If empty
36+
string, download from URL. If None, raise exception.
37+
38+
Returns:
39+
str: Path to zip file, either provided or downloaded.
40+
"""
41+
if zip_file_path:
42+
if not os.path.isfile(zip_file_path):
43+
raise Exception(f'Provided {description} zip file path does not exist: {zip_file_path}')
44+
logger.info(f'Using provided {description} zip file: {zip_file_path}')
45+
yield zip_file_path
46+
else:
47+
downloaded_tmp_zip = _download(url, description)
48+
try: # delete temp file after use, even in exceptions
49+
yield downloaded_tmp_zip
50+
finally:
51+
os.remove(downloaded_tmp_zip)
52+
53+
54+
55+
def _download(url, description):
56+
"""Utility function to download a file from a URL to a temporary file.
57+
58+
Args:
59+
url (str): URL to download from.
60+
description (str): Description of the file being downloaded, for logging.
61+
62+
Returns:
63+
str: Path to the temporary file containing the downloaded content.
64+
"""
65+
logger.info(f'Downloading {description} from {url}...')
66+
67+
try:
68+
with urllib.request.urlopen(url) as response:
69+
if response.getcode() != 200:
70+
raise Exception(f'Failed to download {description} from {url}. Status code: {response.getcode()}')
71+
72+
tmpfile_path = tempfile.NamedTemporaryFile(delete=False).name
73+
with open(tmpfile_path, 'wb') as tmpfile:
74+
tmpfile.write(response.read())
75+
except urllib.error.URLError as e:
76+
raise Exception(f'Failed to download {description} from {url}: {e}')
77+
78+
return tmpfile_path
79+
80+
81+
def _unzip(zip_file_path, target_folder, zip_file_folder_name):
82+
"""Utility function to unzip zip file into ALab folder.
83+
84+
Args:
85+
zip_file_path (str): Path to the zip file.
86+
target_folder (str): Target folder to unzip into.
87+
zip_file_folder_name (str): Top-level folder name inside the zip file to extract.
88+
"""
89+
90+
# assert output folder exists
91+
os.makedirs(target_folder, exist_ok=True)
92+
93+
with ZipFile(zip_file_path, 'r') as zip_file:
94+
for member in zip_file.namelist():
95+
if (zip_file_folder_name + os.sep) not in member:
96+
continue
97+
98+
relative_path = member.split(zip_file_folder_name + os.sep, 1)[1]
99+
if relative_path:
100+
target_path = os.path.join(target_folder, relative_path)
101+
102+
if member.endswith('/'): # a directory
103+
os.makedirs(target_path, exist_ok=True)
104+
else: # a file
105+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
106+
with open(target_path, 'wb') as f:
107+
f.write(zip_file.read(member))
108+
logger.debug(f'{member} >> {target_path}')
109+
110+
111+
def _parse_args():
112+
"""Parse command line arguments.
113+
114+
Returns:
115+
argparse.Namespace: Parsed arguments.
116+
"""
117+
parser = argparse.ArgumentParser(description='Build script for assembling ALab packages.')
118+
119+
default_args = {
120+
'metavar': 'ZIP_FILE',
121+
'nargs': '?', # allow optional .zip file
122+
'const': '', # indicate download default from URL
123+
'default': None, # indicate no action
124+
}
125+
126+
parser.add_argument(
127+
'--output', '-o',
128+
help='Output folder for assembled package (defaults to working directory)',
129+
default=os.getcwd()
130+
)
131+
132+
group = parser.add_argument_group('Install Options', 'Options to install asset packages into ALab repository. Provide a zip file path, or use without argument to download default online package.')
133+
group.add_argument(
134+
'--all',
135+
action='store_true',
136+
help='Install all asset packages. Individual flags can still be overridden to provide .zip files.'
137+
)
138+
group.add_argument(
139+
'--techvar',
140+
help='Install Techvar assets',
141+
**default_args,
142+
)
143+
group.add_argument(
144+
'--baked_procedurals',
145+
help='Install baked procedurals',
146+
**default_args,
147+
)
148+
group.add_argument(
149+
'--texture_pack',
150+
help='Install texture pack',
151+
**default_args,
152+
)
153+
group.add_argument(
154+
'--cameras',
155+
help='Install camera assets',
156+
**default_args,
157+
)
158+
159+
args = parser.parse_args()
160+
logger.debug(f'Parsed arguments: {args}')
161+
162+
if not os.path.isdir(args.output):
163+
parser.error(f'Output folder does not exist: {args.output}')
164+
165+
# if --all is set, set all to default (download)
166+
# but let user override individual options with a .zip file
167+
if args.all:
168+
args.techvar = args.techvar or ''
169+
args.baked_procedurals = args.baked_procedurals or ''
170+
args.texture_pack = args.texture_pack or ''
171+
args.cameras = args.cameras or ''
172+
173+
if not args.all and not any(arg is not None for arg in [args.techvar, args.baked_procedurals, args.texture_pack, args.cameras]):
174+
parser.error(
175+
'No action requested. '
176+
'Add --all to download all packages, or use individual flags: '
177+
'--techvar, --baked_procedurals, --texture_pack, --cameras'
178+
)
179+
180+
return args
181+
182+
183+
## MAIN FUNCTIONS
184+
185+
186+
def install_techvar(zip_file, output_folder):
187+
logger.info('Installing Techvar assets...')
188+
fragment_folder = os.path.join(output_folder, 'ALab', 'fragment')
189+
_unzip(zip_file, fragment_folder, 'fragment')
190+
logger.info('Techvar assets installed successfully.')
191+
192+
193+
def install_baked_procedurals(zip_file, output_folder):
194+
logger.info('Installing baked procedural assets...')
195+
fragment_folder = os.path.join(output_folder, 'ALab', 'baked_procedurals')
196+
_unzip(zip_file, fragment_folder, 'baked_procedurals')
197+
logger.info('Baked procedurals installed successfully.')
198+
199+
200+
def install_texture_pack(zip_file, output_folder):
201+
logger.info('Installing texture pack...')
202+
fragment_folder = os.path.join(output_folder, 'ALab', 'fragment')
203+
_unzip(zip_file, fragment_folder, 'fragment')
204+
logger.info('Texture pack installed successfully.')
205+
206+
207+
def install_cameras(zip_file, output_folder):
208+
logger.info('Installing camera asset package...')
209+
fragment_folder = os.path.join(output_folder, 'ALab')
210+
_unzip(zip_file, fragment_folder, 'trailer_cameras')
211+
logger.info('Cameras installed successfully.')
212+
213+
214+
def install_all(output_folder, techvar=None, baked_procedurals=None, texture_pack=None, cameras=None):
215+
"""Main install function to handle all requested packages.
216+
Args:
217+
218+
output_folder (str): Output folder for the assembled package,
219+
assumed to be a local clone of the ALab GitHub repository.
220+
techvar (str or None): If provided, path to Techvar .zip file. If
221+
empty string, download default Techvar assets. If None, skip installation.
222+
baked_procedurals (str or None): If provided, path to Baked Procedurals
223+
.zip file. If empty string, download default baked procedurals assets. If None, skip installation.
224+
texture_pack (str or None): If provided, path to texture pack .zip file. If
225+
empty string, download default Texture Pack. If None, skip installation.
226+
cameras (str or None): If provided, path to cameras .zip file. If
227+
empty string, download default camera assets. If None, skip installation.
228+
"""
229+
230+
if techvar is not None:
231+
with _get_or_download(TECHVAR_ZIP_URL, 'Techvar assets', techvar) as zip_file:
232+
install_techvar(zip_file, output_folder)
233+
234+
if baked_procedurals is not None:
235+
with _get_or_download(BAKED_PROCEDURALS_ZIP_URL, 'baked procedurals', baked_procedurals) as zip_file:
236+
install_baked_procedurals(zip_file, output_folder)
237+
238+
if texture_pack is not None:
239+
with _get_or_download(TEXTURE_PACK_ZIP_URL, 'texture pack', texture_pack) as zip_file:
240+
install_texture_pack(zip_file, output_folder)
241+
242+
if cameras is not None:
243+
with _get_or_download(CAMERAS_ZIP_URL, 'cameras', cameras) as zip_file:
244+
install_cameras(zip_file, output_folder)
245+
246+
247+
if __name__ == '__main__':
248+
logging.basicConfig(level=logging.INFO)
249+
250+
args = _parse_args()
251+
install_all(
252+
output_folder=args.output,
253+
techvar=args.techvar,
254+
baked_procedurals=args.baked_procedurals,
255+
texture_pack=args.texture_pack,
256+
cameras=args.cameras
257+
)

0 commit comments

Comments
 (0)