Add Chromium-only Blender WebEngine parity work
This commit is contained in:
420
blender-5.2.0/tests/python/bl_blendfile_relationships.py
Normal file
420
blender-5.2.0/tests/python/bl_blendfile_relationships.py
Normal file
@@ -0,0 +1,420 @@
|
||||
# SPDX-FileCopyrightText: 2020-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""
|
||||
blender -b -P tests/python/bl_blendfile_relationships.py --src-test-dir tests/files --output-dir /tmp/blendfile_io/
|
||||
"""
|
||||
__all__ = (
|
||||
"main",
|
||||
)
|
||||
|
||||
import bpy
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from bpy.path import native_pathsep
|
||||
|
||||
_my_dir = Path(__file__).resolve().parent
|
||||
sys.path.append(str(_my_dir))
|
||||
|
||||
from bl_blendfile_utils import TestBlendLibLinkHelper, TestHelper
|
||||
|
||||
|
||||
class TestBlendUserMap(TestBlendLibLinkHelper):
|
||||
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
|
||||
def test_user_map(self):
|
||||
output_dir = self.args.output_dir
|
||||
output_blendfile_path = self.init_lib_data_indirect_lib()
|
||||
|
||||
# Simple link of a single ObData.
|
||||
self.reset_blender()
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=output_blendfile_path)
|
||||
|
||||
self.assertEqual(len(bpy.data.images), 1)
|
||||
self.assertIsNotNone(bpy.data.images[0].library)
|
||||
self.assertEqual(len(bpy.data.materials), 1)
|
||||
self.assertIsNotNone(bpy.data.materials[0].library)
|
||||
self.assertEqual(len(bpy.data.meshes), 1)
|
||||
self.assertEqual(len(bpy.data.objects), 1)
|
||||
self.assertEqual(len(bpy.data.collections), 1)
|
||||
|
||||
user_map = bpy.data.user_map()
|
||||
# Note: Workspaces and screens are ignored here.
|
||||
expected_map = {
|
||||
bpy.data.images[0]: {bpy.data.materials[0]},
|
||||
bpy.data.materials[0]: {bpy.data.meshes[0]},
|
||||
bpy.data.scenes[0]: {bpy.data.window_managers[0]},
|
||||
bpy.data.collections[0]: {bpy.data.scenes[0]},
|
||||
bpy.data.libraries[0]: set(),
|
||||
bpy.data.meshes[0]: {bpy.data.objects[0]},
|
||||
bpy.data.objects[0]: {bpy.data.scenes[0],
|
||||
bpy.data.collections[0]},
|
||||
bpy.data.window_managers[0]: set(),
|
||||
}
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, user_map)
|
||||
self.assertEqual(user_map[k], v, msg=f"ID {k.name} has unexpected user map")
|
||||
|
||||
user_map = bpy.data.user_map(subset=[bpy.data.objects[0], bpy.data.meshes[0]])
|
||||
expected_map = {
|
||||
bpy.data.meshes[0]: {bpy.data.objects[0]},
|
||||
bpy.data.objects[0]: {bpy.data.scenes[0],
|
||||
bpy.data.collections[0]},
|
||||
}
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, user_map)
|
||||
self.assertEqual(user_map[k], v, msg=f"ID {k.name} has unexpected user map")
|
||||
user_map = bpy.data.user_map(key_types={'OBJECT', 'MESH'})
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, user_map)
|
||||
self.assertEqual(user_map[k], v, msg=f"ID {k.name} has unexpected user map")
|
||||
|
||||
user_map = bpy.data.user_map(value_types={'SCENE'})
|
||||
expected_map = {
|
||||
bpy.data.collections[0]: {bpy.data.scenes[0]},
|
||||
bpy.data.objects[0]: {bpy.data.scenes[0]},
|
||||
}
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, user_map)
|
||||
self.assertEqual(user_map[k], v, msg=f"ID {k.name} has unexpected user map")
|
||||
|
||||
# Test handling of invalid parameters
|
||||
self.assertRaises(ValueError, bpy.data.user_map, value_types={'FOOBAR'})
|
||||
self.assertRaises(TypeError, bpy.data.user_map, subset=[bpy.data.objects[0], bpy.data.meshes[0], "FooBar"])
|
||||
|
||||
|
||||
class TestBlendFilePathMap(TestBlendLibLinkHelper):
|
||||
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
|
||||
def test_file_path_map(self):
|
||||
def abspaths(file_path_map):
|
||||
return {k: {os.path.normpath(bpy.path.abspath(p)) for p in v}
|
||||
for k, v in file_path_map.items()}
|
||||
|
||||
output_dir = self.args.output_dir
|
||||
output_blendfile_path = self.init_lib_data_indirect_lib()
|
||||
|
||||
# Simple link of a single ObData.
|
||||
self.reset_blender()
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=output_blendfile_path)
|
||||
|
||||
self.assertEqual(len(bpy.data.images), 1)
|
||||
self.assertIsNotNone(bpy.data.images[0].library)
|
||||
self.assertEqual(len(bpy.data.materials), 1)
|
||||
self.assertIsNotNone(bpy.data.materials[0].library)
|
||||
self.assertEqual(len(bpy.data.meshes), 1)
|
||||
self.assertEqual(len(bpy.data.objects), 1)
|
||||
self.assertEqual(len(bpy.data.collections), 1)
|
||||
|
||||
blendlib_path = os.path.normpath(bpy.path.abspath(bpy.data.materials[0].library.filepath))
|
||||
image_path = os.path.join(native_pathsep(self.args.src_test_dir),
|
||||
native_pathsep('imbuf_io/reference/jpeg-rgb-90__from__rgba08.jpg'))
|
||||
|
||||
file_path_map = abspaths(bpy.data.file_path_map())
|
||||
# Note: Workspaces and screens are ignored here.
|
||||
expected_map = {
|
||||
bpy.data.images[0]: {image_path},
|
||||
bpy.data.materials[0]: set(),
|
||||
bpy.data.scenes[0]: set(),
|
||||
bpy.data.collections[0]: set(),
|
||||
bpy.data.libraries[0]: {blendlib_path},
|
||||
bpy.data.meshes[0]: set(),
|
||||
bpy.data.objects[0]: set(),
|
||||
bpy.data.window_managers[0]: set(),
|
||||
}
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, file_path_map)
|
||||
self.assertEqual(file_path_map[k], v, msg=f"ID {k.name} has unexpected filepath map")
|
||||
|
||||
file_path_map = abspaths(bpy.data.file_path_map(include_libraries=True))
|
||||
# Note: Workspaces and screens are ignored here.
|
||||
expected_map = {
|
||||
bpy.data.images[0]: {image_path, blendlib_path},
|
||||
bpy.data.materials[0]: {blendlib_path},
|
||||
bpy.data.scenes[0]: set(),
|
||||
bpy.data.collections[0]: set(),
|
||||
bpy.data.libraries[0]: {blendlib_path},
|
||||
bpy.data.meshes[0]: set(),
|
||||
bpy.data.objects[0]: set(),
|
||||
bpy.data.window_managers[0]: set(),
|
||||
}
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, file_path_map)
|
||||
self.assertEqual(file_path_map[k], v, msg=f"ID {k.name} has unexpected filepath map")
|
||||
|
||||
file_path_map = abspaths(bpy.data.file_path_map(subset=[bpy.data.images[0], bpy.data.materials[0]]))
|
||||
expected_map = {
|
||||
bpy.data.images[0]: {image_path},
|
||||
bpy.data.materials[0]: set(),
|
||||
}
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, file_path_map)
|
||||
self.assertEqual(file_path_map[k], v, msg=f"ID {k.name} has unexpected filepath map")
|
||||
partial_map = abspaths(bpy.data.file_path_map(key_types={'IMAGE', 'MATERIAL'}))
|
||||
for k, v in expected_map.items():
|
||||
self.assertIn(k, file_path_map)
|
||||
self.assertEqual(file_path_map[k], v, msg=f"ID {k.name} has unexpected filepath map")
|
||||
|
||||
# Test handling of invalid parameters
|
||||
self.assertRaises(ValueError, bpy.data.file_path_map, key_types={'FOOBAR'})
|
||||
self.assertRaises(TypeError, bpy.data.file_path_map, subset=[bpy.data.objects[0], bpy.data.images[0], "FooBar"])
|
||||
|
||||
|
||||
class TestBlendFilePathForeach(TestHelper):
|
||||
testdir: Path
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
# File paths can get long, and thus also so can diffs when things go wrong.
|
||||
self.maxDiff = 10240
|
||||
self.testdir = Path(self.args.src_test_dir) / "libraries_and_linking"
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "library_test_scene.blend"))
|
||||
|
||||
# Name the library data-blocks after their filename, so that they can be reliably identified later.
|
||||
# Otherwise we're stuck with 'Lib', 'Lib.001', etc.
|
||||
for lib_id in bpy.data.libraries[:]:
|
||||
abspath = Path(str(bpy.path.abspath(lib_id.filepath)))
|
||||
lib_id.name = abspath.stem
|
||||
|
||||
def test_without_args(self) -> None:
|
||||
"""Test file_path_foreach() without any arguments, it should report everything but packed files."""
|
||||
|
||||
visited_paths = self._file_path_foreach()
|
||||
self.assertEqual({
|
||||
(bpy.data.libraries['direct_linked_A'], self.testdir / "libraries/direct_linked_A.blend"),
|
||||
(bpy.data.libraries['direct_linked_B'], self.testdir / "libraries/direct_linked_B.blend"),
|
||||
(bpy.data.libraries['indirect_datablocks'], self.testdir / "libraries/indirect_datablocks.blend"),
|
||||
}, visited_paths)
|
||||
|
||||
def test_with_nondefault_flag(self) -> None:
|
||||
"""Test file_path_foreach() with a non-default flag.
|
||||
|
||||
If any non-default flag works, it's enough as a test to assume the flags
|
||||
are parsed and passed to the C++ code correctly. There is no need to
|
||||
exhaustively test all the flags here.
|
||||
"""
|
||||
|
||||
# The default value for `flags` is {'SKIP_PACKED',
|
||||
# 'SKIP_WEAK_REFERENCES'}, so to also see the packed files, only pass
|
||||
# `SKIP_WEAK_REFERENCES`.
|
||||
visited_paths = self._file_path_foreach(flags={'SKIP_WEAK_REFERENCES'})
|
||||
self.assertEqual({
|
||||
(bpy.data.libraries['direct_linked_A'], self.testdir / "libraries/direct_linked_A.blend"),
|
||||
(bpy.data.libraries['direct_linked_B'], self.testdir / "libraries/direct_linked_B.blend"),
|
||||
(bpy.data.libraries['indirect_datablocks'], self.testdir / "libraries/indirect_datablocks.blend"),
|
||||
(bpy.data.images['pack.png'], self.testdir / "libraries/pack.png"),
|
||||
}, visited_paths, "testing without SKIP_PACKED")
|
||||
|
||||
def test_filepath_rewriting(self) -> None:
|
||||
# Store the pre-modification value, as its use of (back)slashes is platform-dependent.
|
||||
image_filepath_before = str(bpy.data.images['pack.png'].filepath)
|
||||
|
||||
def visit_path_fn(owner_id: bpy.types.ID, path: str, _meta) -> str | None:
|
||||
return "//{}-rewritten.blend".format(owner_id.name)
|
||||
bpy.data.file_path_foreach(visit_path_fn)
|
||||
|
||||
libs = bpy.data.libraries
|
||||
self.assertEqual(libs['direct_linked_A'].filepath, "//direct_linked_A-rewritten.blend")
|
||||
self.assertEqual(libs['direct_linked_B'].filepath, "//direct_linked_B-rewritten.blend")
|
||||
self.assertEqual(libs['indirect_datablocks'].filepath, "//indirect_datablocks-rewritten.blend")
|
||||
self.assertEqual(
|
||||
bpy.data.images['pack.png'].filepath,
|
||||
image_filepath_before,
|
||||
"Packed file should not have changed")
|
||||
|
||||
def test_exception_passing(self) -> None:
|
||||
"""Python exceptions in the callback function should be raised by file_path_foreach()."""
|
||||
# Any Python exception should work, not just built-in ones.
|
||||
class CustomException(Exception):
|
||||
pass
|
||||
|
||||
def visit_path_fn(_owner_id: bpy.types.ID, _path: str, _meta) -> str | None:
|
||||
raise CustomException("arg0", 1, "arg2")
|
||||
|
||||
try:
|
||||
bpy.data.file_path_foreach(visit_path_fn)
|
||||
except CustomException as ex:
|
||||
self.assertEqual(("arg0", 1, "arg2"), ex.args, "Parameters passed to the exception should be retained")
|
||||
else:
|
||||
self.fail("Expected exception not thrown")
|
||||
|
||||
def test_meta_parameter(self) -> None:
|
||||
"""The meta argument should be a BlendDataPathMeta."""
|
||||
|
||||
def visit_path_fn(_owner_id: bpy.types.ID, _path: str, meta) -> str | None:
|
||||
self.assertFalse(meta.is_expanded)
|
||||
self.assertFalse(meta.is_cache)
|
||||
self.assertFalse(meta.is_readonly)
|
||||
|
||||
bpy.data.file_path_foreach(visit_path_fn)
|
||||
|
||||
def test_expand_and_texture_caches(self) -> None:
|
||||
"""Expand UDIM tiles, sequence frames and tx files."""
|
||||
import tempfile
|
||||
|
||||
def make_png(path: Path) -> None:
|
||||
img = bpy.data.images.new(
|
||||
name=f"__tmp_{path.stem}", width=4, height=4, alpha=True)
|
||||
img.pixels = [0.5, 0.5, 0.5, 1.0] * (4 * 4)
|
||||
img.filepath_raw = str(path)
|
||||
img.file_format = "PNG"
|
||||
img.save()
|
||||
bpy.data.images.remove(img)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="bl_path_foreach_") as tmp_str:
|
||||
tmp = Path(tmp_str)
|
||||
|
||||
# Make test image files and dummy tx tiles.
|
||||
tile_1001 = tmp / "udim.1001.png"
|
||||
tile_1002 = tmp / "udim.1002.png"
|
||||
make_png(tile_1001)
|
||||
make_png(tile_1002)
|
||||
|
||||
frame_001 = tmp / "seq_001.png"
|
||||
frame_002 = tmp / "seq_002.png"
|
||||
make_png(frame_001)
|
||||
make_png(frame_002)
|
||||
|
||||
individual = tmp / "individual.png"
|
||||
make_png(individual)
|
||||
|
||||
cache_dir = tmp / "blender_tx"
|
||||
cache_dir.mkdir()
|
||||
hex32 = "0123456789ABCDEF0123456789ABCDEF"
|
||||
tx_tile_1001 = cache_dir / f"udim.1001.png.1-{hex32}.tx"
|
||||
tx_tile_1001.write_bytes(b"fake-tx")
|
||||
tx_frame_001 = cache_dir / f"seq_001.png.1-{hex32}.tx"
|
||||
tx_frame_001.write_bytes(b"fake-tx")
|
||||
tx_individual = cache_dir / f"individual.png.1-{hex32}.tx"
|
||||
tx_individual.write_bytes(b"fake-tx")
|
||||
|
||||
# Empty scene save next to images.
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
blend_path = tmp / "scene.blend"
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(blend_path))
|
||||
|
||||
# Load images
|
||||
udim_img = bpy.data.images.load(str(tmp / "udim.<UDIM>.png"))
|
||||
udim_img.source = "TILED"
|
||||
seq_img = bpy.data.images.load(str(frame_001))
|
||||
seq_img.source = "SEQUENCE"
|
||||
bpy.data.images.load(str(individual))
|
||||
|
||||
# Check expected paths are visited.
|
||||
visited: dict[Path, tuple[bool, bool]] = {}
|
||||
|
||||
def visit(owner_id: bpy.types.ID, path: str, meta) -> None:
|
||||
abspath = Path(str(bpy.path.abspath(path, library=owner_id.library))).resolve()
|
||||
visited[abspath] = (meta.is_expanded, meta.is_cache)
|
||||
|
||||
bpy.data.file_path_foreach(
|
||||
visit,
|
||||
flags={
|
||||
"SKIP_PACKED",
|
||||
"SKIP_WEAK_REFERENCES",
|
||||
"EXPAND_TOKENS",
|
||||
"EXPAND_SEQUENCES",
|
||||
"EXPAND_CACHES",
|
||||
},
|
||||
)
|
||||
|
||||
for tile in (tile_1001, tile_1002):
|
||||
self.assertEqual((True, False), visited.get(tile.resolve()),
|
||||
f"missing UDIM tile {tile.name}")
|
||||
for frame in (frame_001, frame_002):
|
||||
self.assertEqual((True, False), visited.get(frame.resolve()),
|
||||
f"missing sequence frame {frame.name}")
|
||||
self.assertEqual((False, False), visited.get(individual.resolve()),
|
||||
"missing individual image path")
|
||||
self.assertEqual((True, True), visited.get(tx_individual.resolve()),
|
||||
"missing individual image texture cache")
|
||||
for tx in (tx_tile_1001, tx_frame_001):
|
||||
self.assertEqual((True, True), visited.get(tx.resolve()),
|
||||
f"missing texture cache {tx.name}")
|
||||
|
||||
@staticmethod
|
||||
def _file_path_foreach(
|
||||
subtypes: set[str] | None = None,
|
||||
visit_keys: set[str] | None = None,
|
||||
flags: set[str] | None = None,
|
||||
) -> set[tuple[bpy.types.ID, Path]]:
|
||||
"""Call bpy.data.file_path_foreach(), returning the visited paths as set of (datablock, path) tuples.
|
||||
|
||||
A set is used because the order of visiting is not relevant.
|
||||
"""
|
||||
visisted_paths: set[tuple[bpy.types.ID, Path]] = set()
|
||||
|
||||
def visit_path_fn(owner_id: bpy.types.ID, path: str, _meta) -> str | None:
|
||||
abspath = Path(str(bpy.path.abspath(path, library=owner_id.library)))
|
||||
visisted_paths.add((owner_id, abspath))
|
||||
|
||||
# Dynamically build the keyword arguments, because the None values are not
|
||||
# valid, and should be encoded as not having the kwarg.
|
||||
kwargs = {}
|
||||
if subtypes is not None:
|
||||
kwargs['subtypes'] = subtypes
|
||||
if visit_keys is not None:
|
||||
kwargs['visit_keys'] = visit_keys
|
||||
if flags is not None:
|
||||
kwargs['flags'] = flags
|
||||
|
||||
bpy.data.file_path_foreach(visit_path_fn, **kwargs)
|
||||
|
||||
return visisted_paths
|
||||
|
||||
|
||||
TESTS = (
|
||||
TestBlendUserMap,
|
||||
TestBlendFilePathMap,
|
||||
TestBlendFilePathForeach,
|
||||
)
|
||||
|
||||
|
||||
def argparse_create():
|
||||
import argparse
|
||||
|
||||
# When --help or no args are given, print this help
|
||||
description = "Test basic relationship info of loaded data."
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser.add_argument(
|
||||
"--src-test-dir",
|
||||
dest="src_test_dir",
|
||||
default=".",
|
||||
help="Where to find test/data root directory",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
dest="output_dir",
|
||||
help="Where to output temp saved blendfiles",
|
||||
required=True,
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
args = argparse_create().parse_args()
|
||||
|
||||
# Don't write thumbnails into the home directory.
|
||||
bpy.context.preferences.filepaths.file_preview_type = 'NONE'
|
||||
|
||||
for Test in TESTS:
|
||||
Test(args).run_all_tests()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
||||
main()
|
||||
Reference in New Issue
Block a user