Merge branch 'blender-v4.4-release'
[blender.git] / tests / utils / batch_load_blendfiles.py
blob80444582371893bf39c4e1612e89a786759ec4f0
1 # SPDX-FileCopyrightText: 2011-2023 Blender Authors
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 __all__ = (
6 "main",
9 r"""
10 Example usage:
12 blender --factory-startup --python ./tests/utils/batch_load_blendfiles.py
14 Arguments may be passed in:
16 blender --factory-startup --python ./tests/utils/batch_load_blendfiles.py -- --sort-by=SIZE --range=0:10 --wait=0.1
17 """
18 import os
19 import sys
20 import argparse
22 from collections.abc import (
23 Iterator,
26 SOURCE_DIR = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")))
27 LIB_DIR = os.path.abspath(os.path.normpath(os.path.join(SOURCE_DIR, "lib")))
29 SORT_BY_FN = {
30 "PATH": lambda path: path,
31 "SIZE": lambda path: os.path.getsize(path),
35 def blend_list(path: str) -> Iterator[str]:
36 for dirpath, dirnames, filenames in os.walk(path):
37 # skip '.git'
38 dirnames[:] = [d for d in dirnames if not d.startswith(".")]
39 for filename in filenames:
40 if filename.lower().endswith(".blend"):
41 filepath = os.path.join(dirpath, filename)
42 yield filepath
45 def print_load_message(filepath: str, index: int) -> None:
46 msg = "({:d}): {:s}".format(index, filepath)
47 print("=" * len(msg))
48 print(msg)
49 print("=" * len(msg))
52 def load_blend_file(filepath: str) -> None:
53 import bpy # type: ignore
54 bpy.ops.wm.open_mainfile(filepath=filepath)
57 def load_files_immediately(blend_files: list[str], blend_file_index_offset: int) -> None:
58 index = blend_file_index_offset
59 for filepath in blend_files:
60 print_load_message(filepath, index)
61 index += 1
62 load_blend_file(filepath)
65 def load_files_with_wait(blend_files: list[str], blend_file_index_offset: int, wait: float) -> None:
66 index = 0
68 def load_on_timer() -> float | None:
69 nonlocal index
70 if index >= len(blend_files):
71 sys.exit(0)
73 filepath = blend_files[index]
74 print_load_message(filepath, index + blend_file_index_offset)
75 index += 1
77 load_blend_file(filepath)
78 return wait
80 import bpy
81 bpy.app.timers.register(load_on_timer, persistent=True)
84 def argparse_handle_int_range(value: str) -> tuple[int, int]:
85 range_beg, sep, range_end = value.partition(":")
86 if not sep:
87 raise argparse.ArgumentTypeError("Expected a \":\" separator!")
88 try:
89 result = int(range_beg), int(range_end)
90 except Exception as ex:
91 raise argparse.ArgumentTypeError("Expected two integers: {!s}".format(ex))
92 return result
95 def argparse_create() -> argparse.ArgumentParser:
96 import argparse
97 sort_by_choices = tuple(sorted(SORT_BY_FN.keys()))
99 # When `--help` or no arguments are given, print this help.
100 epilog = "Use to automate loading many blend files in a single Blender instance."
102 parser = argparse.ArgumentParser(
103 formatter_class=argparse.RawTextHelpFormatter,
104 description=__doc__,
105 epilog=epilog,
108 parser.add_argument(
109 "--blend-dir",
110 dest="blend_dir",
111 metavar='BLEND_DIR',
112 default=LIB_DIR,
113 required=False,
114 help="Path to recursively search blend files.",
117 parser.add_argument(
118 '--sort-by',
119 dest='files_sort_by',
120 choices=sort_by_choices,
121 default="PATH",
122 required=False,
123 metavar='SORT_METHOD',
124 help='Order to load files {:s}.'.format(repr(sort_by_choices)),
127 parser.add_argument(
128 '--range',
129 dest='files_range',
130 type=argparse_handle_int_range,
131 required=False,
132 default=(0, sys.maxsize),
133 metavar='RANGE',
134 help=(
135 "The beginning and end range separated by a \":\", e.g."
136 "useful for loading a range of files known to cause problems."
140 parser.add_argument(
141 "--wait",
142 dest="wait",
143 type=float,
144 default=-1.0,
145 required=False,
146 help=(
147 "Time to wait between loading files, "
148 "implies redrawing and even allows user interaction (-1.0 to disable)."
152 return parser
155 def main() -> int | None:
156 try:
157 argv_sep = sys.argv.index("--")
158 except ValueError:
159 argv_sep = -1
161 argv = [] if argv_sep == -1 else sys.argv[argv_sep + 1:]
162 args = argparse_create().parse_args(argv)
163 del argv
165 if not os.path.exists(args.blend_dir):
166 sys.stderr.write("Path {!r} not found!\n".format(args.blend_dir))
167 return 1
168 blend_files = list(blend_list(args.blend_dir))
169 if not blend_files:
170 sys.stderr.write("No blend files in {!r}!\n".format(args.blend_dir))
171 return 1
173 blend_files.sort(key=SORT_BY_FN[args.files_sort_by])
175 range_beg, range_end = args.files_range
177 blend_files_total = len(blend_files)
179 blend_files = blend_files[range_beg:range_end]
181 print("Found {:,d} files within {!r}".format(blend_files_total, args.blend_dir))
182 if len(blend_files) != blend_files_total:
183 print("Using a sub-range of {:,d}".format(len(blend_files)))
185 if args.wait == -1.0:
186 load_files_immediately(blend_files, range_beg)
187 else:
188 load_files_with_wait(blend_files, range_beg, args.wait)
189 return None
191 return 0
194 if __name__ == "__main__":
195 result = main()
196 if result is not None:
197 sys.exit(result)