-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun_step3a_render.py
More file actions
237 lines (204 loc) · 6.97 KB
/
run_step3a_render.py
File metadata and controls
237 lines (204 loc) · 6.97 KB
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
import argparse
import os
import platform
import re
import shutil
import subprocess
import sys
from pathlib import Path
ROOT_DIR = Path(__file__).resolve().parent
DEFAULT_INPUT_DIR = ROOT_DIR / "output" / "experiment_data" / "03_designs_H_gpt-4o"
DEFAULT_SCENE_FILE = ROOT_DIR / "ria" / "visualization" / "230125_render_enviroment.blend"
DEFAULT_BATCH_SCRIPT = ROOT_DIR / "ria" / "visualization" / "batch_ops.py"
BLENDER_ENV_VAR = "BLENDER_BIN"
def parse_args():
parser = argparse.ArgumentParser(description="Run batch Blender renders for GH outputs.")
parser.add_argument(
"--input-dir",
default=str(DEFAULT_INPUT_DIR),
help="Root directory containing GH output folders, or a single design folder with OBJ files.",
)
parser.add_argument(
"--scene-file",
default=str(DEFAULT_SCENE_FILE),
help="Blender scene file to open for rendering.",
)
parser.add_argument(
"--blender-bin",
default=None,
help=f"Explicit Blender executable. Overrides {BLENDER_ENV_VAR} and auto-detection.",
)
parser.add_argument(
"--render-style",
default="GHOSTED",
choices=["SOLID", "GHOSTED", "WIREFRAME"],
help="Render style to request from the Blender batch script.",
)
parser.add_argument(
"--resolution-x",
type=int,
default=1280,
help="Render width in pixels.",
)
parser.add_argument(
"--resolution-y",
type=int,
default=1280,
help="Render height in pixels.",
)
parser.add_argument(
"--samples",
type=int,
default=300,
help="Cycles sample count.",
)
parser.add_argument(
"--overwrite",
action="store_true",
help="Re-render images even if they already exist.",
)
parser.add_argument(
"--y-up",
action="store_true",
help="Import OBJ files as Y-up instead of Z-up.",
)
parser.add_argument(
"--global-scale",
action="store_true",
help="Use one shared scale factor across all variants in a design folder.",
)
parser.add_argument(
"--limit",
type=int,
default=None,
help="Maximum number of design folders to render.",
)
parser.add_argument(
"--show-ui",
action="store_true",
help="Launch Blender with its UI instead of background mode.",
)
return parser.parse_args()
def blender_candidates():
seen = set()
candidates = []
def add(candidate):
if not candidate:
return
candidate = str(candidate)
if candidate in seen:
return
seen.add(candidate)
candidates.append(Path(candidate))
add(os.getenv(BLENDER_ENV_VAR))
system = platform.system()
if system == "Darwin":
add("/Applications/Blender 3.6.app/Contents/MacOS/blender")
add(str(Path.home() / "Applications" / "Blender 3.6.app" / "Contents" / "MacOS" / "blender"))
elif system == "Windows":
program_files = os.environ.get("ProgramFiles", r"C:\Program Files")
add(Path(program_files) / "Blender Foundation" / "Blender 3.6" / "blender.exe")
else:
add("/usr/bin/blender")
add("/usr/local/bin/blender")
add("/snap/bin/blender")
add(shutil.which("blender"))
return candidates
def blender_version(blender_bin):
try:
result = subprocess.run(
[str(blender_bin), "--version"],
check=False,
capture_output=True,
text=True,
timeout=20,
)
except (OSError, subprocess.TimeoutExpired):
return None
output = "\n".join(part for part in [result.stdout, result.stderr] if part)
match = re.search(r"Blender\s+(\d+\.\d+(?:\.\d+)?)", output)
if match:
return match.group(1)
return None
def find_blender_bin(explicit_path=None):
mismatched_versions = []
candidates = [Path(explicit_path)] if explicit_path else blender_candidates()
for candidate in candidates:
if not candidate or not candidate.exists():
continue
version = blender_version(candidate)
if version is None:
continue
if version.startswith("3.6"):
return candidate, version
mismatched_versions.append((candidate, version))
if explicit_path:
raise FileNotFoundError(
f"Blender 3.6 was not found at the requested path: {explicit_path}"
)
mismatch_summary = ", ".join(f"{path} ({version})" for path, version in mismatched_versions)
if mismatch_summary:
raise RuntimeError(
"Found Blender executables, but none are version 3.6. "
f"Detected: {mismatch_summary}. "
f"Install Blender 3.6 or set {BLENDER_ENV_VAR} / --blender-bin to the 3.6 executable."
)
raise FileNotFoundError(
"Blender 3.6 was not found. Install Blender 3.6 or set "
f"{BLENDER_ENV_VAR} / --blender-bin to the Blender 3.6 executable."
)
def validate_paths(scene_file, input_dir):
scene_path = Path(scene_file)
input_path = Path(input_dir)
if not scene_path.exists():
raise FileNotFoundError(f"Blender scene file not found: {scene_path}")
if not DEFAULT_BATCH_SCRIPT.exists():
raise FileNotFoundError(f"Blender batch script not found: {DEFAULT_BATCH_SCRIPT}")
if not input_path.exists():
raise FileNotFoundError(f"Render input directory not found: {input_path}")
return scene_path, input_path
def build_command(blender_bin, scene_file, args):
command = [str(blender_bin), str(scene_file)]
if not args.show_ui:
command.append("--background")
command.extend(
[
"--python",
str(DEFAULT_BATCH_SCRIPT),
"--",
"--input-dir",
str(Path(args.input_dir).resolve()),
"--render-style",
args.render_style,
"--resolution-x",
str(args.resolution_x),
"--resolution-y",
str(args.resolution_y),
"--samples",
str(args.samples),
]
)
if args.overwrite:
command.append("--overwrite")
if args.y_up:
command.append("--y-up")
if args.global_scale:
command.append("--global-scale")
if args.limit is not None:
command.extend(["--limit", str(args.limit)])
return command
def main():
args = parse_args()
scene_file, _ = validate_paths(args.scene_file, args.input_dir)
blender_bin, blender_version_str = find_blender_bin(args.blender_bin)
print(f"Using Blender {blender_version_str}: {blender_bin}")
print(f"Using scene file: {scene_file}")
print(f"Rendering input: {Path(args.input_dir).resolve()}")
command = build_command(blender_bin, scene_file, args)
print("Launching Blender batch render...")
subprocess.run(command, check=True)
if __name__ == "__main__":
try:
main()
except subprocess.CalledProcessError as exc:
sys.exit(exc.returncode)