forked from bartvdbraak/blender
5126677c6f
Useful for finding regression in file reading as well as issues reported by ASAN, including memory leaks.
197 lines
5.3 KiB
Python
197 lines
5.3 KiB
Python
# SPDX-FileCopyrightText: 2011-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
r"""
|
|
Example usage:
|
|
|
|
blender --factory-startup --python ./tests/utils/batch_load_blendfiles.py
|
|
|
|
Arguments may be passed in:
|
|
|
|
blender --factory-startup --python ./tests/utils/batch_load_blendfiles.py -- --sort-by=SIZE --range=0:10 --wait=0.1
|
|
"""
|
|
import os
|
|
import sys
|
|
import argparse
|
|
|
|
from typing import (
|
|
Generator,
|
|
List,
|
|
Optional,
|
|
Tuple,
|
|
)
|
|
|
|
SOURCE_DIR = os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
|
LIB_DIR = os.path.abspath(os.path.normpath(os.path.join(SOURCE_DIR, "..", "lib")))
|
|
|
|
SORT_BY_FN = {
|
|
"PATH": lambda path: path,
|
|
"SIZE": lambda path: os.path.getsize(path),
|
|
}
|
|
|
|
|
|
def blend_list(path: str) -> Generator[str, None, None]:
|
|
for dirpath, dirnames, filenames in os.walk(path):
|
|
# skip '.git'
|
|
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
|
for filename in filenames:
|
|
if filename.lower().endswith(".blend"):
|
|
filepath = os.path.join(dirpath, filename)
|
|
yield filepath
|
|
|
|
|
|
def print_load_message(filepath: str, index: int) -> None:
|
|
msg = "({:d}): {:s}".format(index, filepath)
|
|
print("=" * len(msg))
|
|
print(msg)
|
|
print("=" * len(msg))
|
|
|
|
|
|
def load_blend_file(filepath: str) -> None:
|
|
import bpy # type: ignore
|
|
bpy.ops.wm.open_mainfile(filepath=filepath)
|
|
|
|
|
|
def load_files_immediately(blend_files: List[str], blend_file_index_offset: int) -> None:
|
|
index = blend_file_index_offset
|
|
for filepath in blend_files:
|
|
print_load_message(filepath, index)
|
|
index += 1
|
|
load_blend_file(filepath)
|
|
|
|
|
|
def load_files_with_wait(blend_files: List[str], blend_file_index_offset: int, wait: float) -> None:
|
|
index = 0
|
|
|
|
def load_on_timer() -> Optional[float]:
|
|
nonlocal index
|
|
if index >= len(blend_files):
|
|
sys.exit(0)
|
|
|
|
filepath = blend_files[index]
|
|
print_load_message(filepath, index + blend_file_index_offset)
|
|
index += 1
|
|
|
|
load_blend_file(filepath)
|
|
return wait
|
|
|
|
import bpy
|
|
bpy.app.timers.register(load_on_timer, persistent=True)
|
|
|
|
|
|
def argparse_handle_int_range(value: str) -> Tuple[int, int]:
|
|
range_beg, sep, range_end = value.partition(":")
|
|
if not sep:
|
|
raise argparse.ArgumentTypeError("Expected a \":\" separator!")
|
|
try:
|
|
result = int(range_beg), int(range_end)
|
|
except Exception as ex:
|
|
raise argparse.ArgumentTypeError("Expected two integers: {!s}".format(ex))
|
|
return result
|
|
|
|
|
|
def argparse_create() -> argparse.ArgumentParser:
|
|
import argparse
|
|
sort_by_choices = tuple(sorted(SORT_BY_FN.keys()))
|
|
|
|
# When `--help` or no arguments are given, print this help.
|
|
epilog = "This is typically used from the output of a stack-trace on Linux/Unix."
|
|
|
|
parser = argparse.ArgumentParser(
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
description=__doc__,
|
|
epilog=epilog,
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--blend-dir",
|
|
dest="blend_dir",
|
|
metavar='BLEND_DIR',
|
|
default=LIB_DIR,
|
|
required=False,
|
|
help="Path to recursively search blend files.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--sort-by',
|
|
dest='files_sort_by',
|
|
choices=sort_by_choices,
|
|
default="PATH",
|
|
required=False,
|
|
metavar='SORT_METHOD',
|
|
help='Order to load files {:s}.'.format(repr(sort_by_choices)),
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--range',
|
|
dest='files_range',
|
|
type=argparse_handle_int_range,
|
|
required=False,
|
|
default=(0, sys.maxsize),
|
|
metavar='RANGE',
|
|
help=(
|
|
"The beginning and end range separated by a \":\", e.g."
|
|
"useful for loading a range of files known to cause problems."
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--wait",
|
|
dest="wait",
|
|
type=float,
|
|
default=-1.0,
|
|
required=False,
|
|
help=(
|
|
"Time to wait between loading files, "
|
|
"implies redrawing and even allows user interaction (-1.0 to disable)."
|
|
),
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def main() -> Optional[int]:
|
|
try:
|
|
argv_sep = sys.argv.index("--")
|
|
except ValueError:
|
|
argv_sep = -1
|
|
|
|
argv = [] if argv_sep == -1 else sys.argv[argv_sep + 1:]
|
|
args = argparse_create().parse_args(argv)
|
|
del argv
|
|
|
|
if not os.path.exists(args.blend_dir):
|
|
sys.stderr.write("Path {!r} not found!\n".format(args.blend_dir))
|
|
return 1
|
|
blend_files = list(blend_list(args.blend_dir))
|
|
if not blend_files:
|
|
sys.stderr.write("No blend files in {!r}!\n".format(args.blend_dir))
|
|
return 1
|
|
|
|
blend_files.sort(key=SORT_BY_FN[args.files_sort_by])
|
|
|
|
range_beg, range_end = args.files_range
|
|
|
|
blend_files_total = len(blend_files)
|
|
|
|
blend_files = blend_files[range_beg:range_end]
|
|
|
|
print("Found {:,d} files within {!r}".format(blend_files_total, args.blend_dir))
|
|
if len(blend_files) != blend_files_total:
|
|
print("Using a sub-range of {:,d}".format(len(blend_files)))
|
|
|
|
if args.wait == -1.0:
|
|
load_files_immediately(blend_files, range_beg)
|
|
else:
|
|
load_files_with_wait(blend_files, range_beg, args.wait)
|
|
return None
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
result = main()
|
|
if result is not None:
|
|
sys.exit(result)
|