forked from DLR-RM/BlenderProc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.py
305 lines (267 loc) · 15.7 KB
/
run.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
import argparse
import os
from os.path import join
import tarfile
import zipfile
import subprocess
import shutil
import signal
import sys
from sys import platform, version_info
if version_info.major == 3:
from urllib.request import urlretrieve
else:
from urllib import urlretrieve
import contextlib
import uuid
from src.utility.ConfigParser import ConfigParser
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('config', default=None, nargs='?', help='The path to the configuration file which describes what the pipeline should do.')
parser.add_argument('args', metavar='arguments', nargs='*', help='Additional arguments which are used to replace placeholders inside the configuration. <args:i> is hereby replaced by the i-th argument.')
parser.add_argument('--reinstall-packages', dest='reinstall_packages', action='store_true', help='If given, all python packages configured inside the configuration file will be reinstalled.')
parser.add_argument('--reinstall-blender', dest='reinstall_blender', action='store_true', help='If given, the blender installation is deleted and reinstalled. Is ignored, if a "custom_blender_path" is configured in the configuration file.')
parser.add_argument('--batch_process', help='Renders a batch of house-cam combinations, by reading a file containing the combinations on each line, where each line is the standard placeholder arguments for rendering a single scene separated by spaces. The value of this option is the path to the index file, no need to add placeholder arguments.')
parser.add_argument('--temp-dir', dest='temp_dir', default=None, help="The path to a directory where all temporary output files should be stored. If it doesn't exist, it is created automatically. Type: string. Default: \"/dev/shm\" or \"/tmp/\" depending on which is available.")
parser.add_argument('--keep-temp-dir', dest='keep_temp_dir', action='store_true', help="If set, the temporary directory is not removed in the end.")
parser.add_argument('-h', '--help', dest='help', action='store_true', help='Show this help message and exit.')
args = parser.parse_args()
if args.config is None:
print(parser.format_help())
exit(0)
config_parser = ConfigParser()
config = config_parser.parse(args.config, args.args, args.help, skip_arg_placeholders=(args.batch_process != None)) # Don't parse placeholder args in batch mode.
setup_config = config["setup"]
# If blender should be downloaded automatically
if "custom_blender_path" not in setup_config:
# Determine path where blender should be installed
if "blender_install_path" in setup_config:
blender_install_path = os.path.expanduser(setup_config["blender_install_path"])
if blender_install_path.startswith("/home_local") and not os.path.exists("/home_local"):
user_name = os.getenv("USERNAME") if platform == "win32" else os.getenv("USER")
home_path = os.getenv("USERPROFILE") if platform == "win32" else os.getenv("HOME")
print("Warning: Changed install path from {}... to {}..., there is no /home_local/ "
"on this machine.".format(join("/home_local", user_name), home_path))
# Replace the seperator from '/' to the os-specific one
# Since all example config files use '/' as seperator
blender_install_path = blender_install_path.replace('/'.join(["/home_local", user_name]), home_path, 1)
blender_install_path = blender_install_path.replace('/', os.path.sep)
else:
blender_install_path = "blender"
# Determine configured version
# right new only support blender-2.91
major_version = "2.91"
minor_version = "0"
blender_version = "blender-{}.{}".format(major_version, minor_version)
if platform == "linux" or platform == "linux2":
blender_version += "-linux64"
blender_path = os.path.join(blender_install_path, blender_version)
elif platform == "darwin":
blender_version += "-macOS"
blender_path = os.path.join(blender_install_path, "Blender.app")
elif platform == "win32":
blender_version += "-windows64"
blender_path = os.path.join(blender_install_path, blender_version)
else:
raise Exception("This system is not supported yet: {}".format(platform))
# If forced reinstall is demanded, remove existing files
if os.path.exists(blender_path) and args.reinstall_blender:
print("Removing existing blender installation")
shutil.rmtree(blender_path)
# Download blender if it not already exists
if not os.path.exists(blender_path):
if version_info.major != 3:
try:
import lzma
except ImportError as e:
print("For decompressing \".xz\" files in python 2.x is it necessary to use lzma")
raise e # from import lzma -> pip install --user pyliblzma
if platform == "linux" or platform == "linux2":
url = "https://download.blender.org/release/Blender" + major_version + "/" + blender_version + ".tar.xz"
elif platform == "darwin":
url = "https://download.blender.org/release/Blender" + major_version + "/" + blender_version + ".dmg"
elif platform == "win32":
url = "https://download.blender.org/release/Blender" + major_version + "/" + blender_version + ".zip"
else:
raise Exception("This system is not supported yet: {}".format(platform))
try:
import progressbar
class DownloadProgressBar(object):
def __init__(self):
self.pbar = None
def __call__(self, block_num, block_size, total_size):
if not self.pbar:
self.pbar = progressbar.ProgressBar(maxval=total_size)
self.pbar.start()
downloaded = block_num * block_size
if downloaded < total_size:
self.pbar.update(downloaded)
else:
self.pbar.finish()
print("Downloading blender from " + url)
file_tmp = urlretrieve(url, None, DownloadProgressBar())[0]
except ImportError:
print("Progressbar for downloading, can only be shown, "
"when the python package \"progressbar\" is installed")
file_tmp = urlretrieve(url, None)[0]
if platform == "linux" or platform == "linux2":
if version_info.major == 3:
with tarfile.open(file_tmp) as tar:
tar.extractall(blender_install_path)
else:
with contextlib.closing(lzma.LZMAFile(file_tmp)) as xz:
with tarfile.open(fileobj=xz) as f:
f.extractall(blender_install_path)
elif platform == "darwin":
if not os.path.exists(blender_install_path):
os.makedirs(blender_install_path)
os.rename(file_tmp, os.path.join(blender_install_path, blender_version + ".dmg"))
# installing the blender app by mounting it and extracting the information
subprocess.Popen(["hdiutil attach {}".format(os.path.join(blender_install_path, blender_version + ".dmg"))],
shell=True).wait()
subprocess.Popen(
["cp -r {} {}".format(os.path.join("/", "Volumes", "Blender", "Blender.app"), blender_install_path)],
shell=True).wait()
subprocess.Popen(["diskutil unmount {}".format(os.path.join("/", "Volumes", "Blender"))], shell=True)
# removing the downloaded image again
subprocess.Popen(["rm {}".format(os.path.join(blender_install_path, blender_version + ".dmg"))], shell=True).wait()
# add Blender.app path to it
elif platform == "win32":
with zipfile.ZipFile(file_tmp) as z:
z.extractall(blender_install_path)
else:
blender_path = os.path.expanduser(setup_config["custom_blender_path"])
# Try to get major version of given blender installation
major_version = None
for sub_dir in os.listdir(blender_path):
# Search for the subdirectory which has the major version as its name (e.q. 2.79)
if os.path.isdir(os.path.join(blender_path, sub_dir)) and sub_dir.replace(".", "").isdigit():
major_version = sub_dir
break
if major_version is None:
raise Exception("Could not determine major blender version")
print("Using blender in " + blender_path)
general_required_packages = ["pyyaml==5.1.2", "imageio", "gitpython"]
required_packages = general_required_packages
if "pip" in setup_config:
required_packages += setup_config["pip"]
# Install required packages
if len(required_packages) > 0:
# Install pip
if platform == "linux" or platform == "linux2":
python_bin_folder = os.path.join(blender_path, major_version, "python", "bin")
python_bin = os.path.join(python_bin_folder, "python3.7m")
packages_path = os.path.abspath(os.path.join(blender_path, "custom-python-packages"))
pre_python_package_path = os.path.join(blender_path, major_version, "python", "lib", "python3.7", "site-packages")
elif platform == "darwin":
python_bin_folder = os.path.join(blender_path, "Contents", "Resources", major_version, "python", "bin")
python_bin = os.path.join(python_bin_folder, "python3.7m")
packages_path = os.path.abspath(os.path.join(
blender_path, "Contents", "Resources", "custom-python-packages"))
pre_python_package_path = os.path.join(blender_path, "Contents", "Resources", major_version, "python", "lib", "python3.7", "site-packages")
elif platform == "win32":
python_bin_folder = os.path.join(blender_path, major_version, "python", "bin")
python_bin = os.path.join(python_bin_folder, "python")
packages_path = os.path.abspath(os.path.join(blender_path, "custom-python-packages"))
pre_python_package_path = os.path.join(blender_path, major_version, "python", "lib", "site-packages")
else:
raise Exception("This system is not supported yet: {}".format(platform))
subprocess.Popen([python_bin, "-m", "ensurepip"], env=dict(os.environ, PYTHONPATH="")).wait()
# Make sure pip is up-to-date
subprocess.Popen([python_bin, "-m", "pip", "install", "--upgrade", "pip"], env=dict(os.environ, PYTHONPATH="")).wait()
# Make sure to not install into the default site-packages path, as this would overwrite already pre-installed packages
if not os.path.exists(packages_path):
os.mkdir(packages_path)
used_env = dict(os.environ, PYTHONPATH=packages_path + ":" + pre_python_package_path)
# Collect already installed packages by calling pip list (outputs: <package name>==<version>)
installed_packages = subprocess.check_output([python_bin, "-m", "pip", "list", "--format=freeze",
"--path={}".format(pre_python_package_path)])
installed_packages += subprocess.check_output([python_bin, "-m", "pip", "list", "--format=freeze",
"--path={}".format(packages_path)])
# Split up strings into two lists (names and versions)
installed_packages_name, installed_packages_versions = zip(*[str(line).lower().split('==') for line in installed_packages.splitlines()])
installed_packages_name = [ele[2:] if ele.startswith("b'") else ele for ele in installed_packages_name]
installed_packages_versions = [ele[:-1] if ele.endswith("'") else ele for ele in installed_packages_versions]
# Install all packages
for package in required_packages:
# Extract name and target version
if "==" in package:
package_name, package_version = package.lower().split('==')
else:
package_name, package_version = package.lower(), None
# If the package is given via git, extract package name from url
if package_name.startswith("git+"):
# Extract part after last slash
package_name = package_name[package_name.rfind("/") + 1:]
# Replace underscores with dashes as its done by pip
package_name = package_name.replace("_", "-")
# Check if package is installed
already_installed = package_name in installed_packages_name
# If version check is necessary
if package_version is not None and already_installed:
# Check if the correct version is installed
already_installed = (package_version == installed_packages_versions[installed_packages_name.index(package_name)])
print("{}:{} was installed: {}".format(package_name, package_version, already_installed))
# If there is already a different version installed
if not already_installed:
# Remove the old version (We have to do this manually, as we are using --target with pip install. There old version are not removed)
subprocess.Popen([python_bin, "-m", "pip", "uninstall", package_name, "-y"],
env=dict(os.environ, PYTHONPATH=packages_path)).wait()
# Only install if its not already installed (pip would check this itself, but at first downloads the requested package which of course always takes a while)
if not already_installed or args.reinstall_packages:
subprocess.Popen([python_bin, "-m", "pip", "install", package, "--target", packages_path,
"--upgrade"], env=dict(os.environ, PYTHONPATH=packages_path)).wait()
# Run script
if platform == "linux" or platform == "linux2":
blender_run_path = os.path.join(blender_path, "blender")
elif platform == "darwin":
blender_run_path = os.path.join(blender_path, "Contents", "MacOS", "Blender")
elif platform == "win32":
blender_run_path = os.path.join(blender_path, "blender")
else:
raise Exception("This system is not supported yet: {}".format(platform))
repo_root_directory = os.path.dirname(os.path.realpath(__file__))
path_src_run = os.path.join(repo_root_directory, "src/run.py")
# Determine perfect temp dir
if args.temp_dir is None:
if sys.platform != "win32":
if os.path.exists("/dev/shm"):
temp_dir = "/dev/shm"
else:
temp_dir = "/tmp"
else:
temp_dir = os.getenv("TEMP")
else:
temp_dir = args.temp_dir
# Generate unique directory name in temp dir
temp_dir = os.path.join(temp_dir, "blender_proc_" + str(uuid.uuid4().hex))
# Create the temp dir
print("Using temporary directory: " + temp_dir)
if not os.path.exists(temp_dir):
os.makedirs(temp_dir)
if not args.batch_process:
p = subprocess.Popen([blender_run_path, "--background", "--python-exit-code", "2", "--python", path_src_run, "--", args.config, temp_dir] + args.args,
env=dict(os.environ, PYTHONPATH=""), cwd=repo_root_directory)
else: # Pass the index file path containing placeholder args for all input combinations (cam, house, output path)
p = subprocess.Popen([blender_run_path, "--background", "--python-exit-code", "2", "--python", path_src_run, "--", args.config, temp_dir, "--batch-process", args.batch_process],
env=dict(os.environ, PYTHONPATH=""), cwd=repo_root_directory)
def clean_temp_dir():
# If temp dir should not be kept and temp dir still exists => remove it
if not args.keep_temp_dir and os.path.exists(temp_dir):
print("Cleaning temporary directory")
shutil.rmtree(temp_dir)
# Listen for SIGTERM signal, so we can properly cleanup and and terminate the child process
def handle_sigterm(signum, frame):
clean_temp_dir()
p.terminate()
signal.signal(signal.SIGTERM, handle_sigterm)
try:
p.wait()
except KeyboardInterrupt:
try:
p.terminate()
except OSError:
pass
p.wait()
# Clean up
clean_temp_dir()
exit(p.returncode)