Code checking for expected amount of local/linked/missing linked/liboverrides IDs is now cleaner and more efficient. Also add a test where the whole content of the source library is removed, to validate that liboverrides using these linked IDs as reference remain available as 'placeholders'.
953 lines
47 KiB
Python
953 lines
47 KiB
Python
# SPDX-FileCopyrightText: 2021-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# ./blender.bin --background --python tests/python/bl_blendfile_library_overrides.py -- --output-dir=/tmp/
|
|
import pathlib
|
|
import bpy
|
|
import sys
|
|
import os
|
|
|
|
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
|
from bl_blendfile_utils import TestHelper
|
|
|
|
|
|
class TestLibraryOverrides(TestHelper):
|
|
MESH_LIBRARY_PARENT = "LibMeshParent"
|
|
OBJECT_LIBRARY_PARENT = "LibMeshParent"
|
|
MESH_LIBRARY_CHILD = "LibMeshChild"
|
|
OBJECT_LIBRARY_CHILD = "LibMeshChild"
|
|
MESH_LIBRARY_PERMISSIVE = "LibMeshPermissive"
|
|
OBJECT_LIBRARY_PERMISSIVE = "LibMeshPermissive"
|
|
|
|
def __init__(self, args):
|
|
super().__init__(args)
|
|
|
|
output_dir = pathlib.Path(self.args.output_dir)
|
|
self.ensure_path(str(output_dir))
|
|
self.output_path = output_dir / "blendlib_overrides.blend"
|
|
self.test_output_path = output_dir / "blendlib_overrides_test.blend"
|
|
|
|
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
|
|
mesh = bpy.data.meshes.new(TestLibraryOverrides.MESH_LIBRARY_PARENT)
|
|
obj = bpy.data.objects.new(TestLibraryOverrides.OBJECT_LIBRARY_PARENT, object_data=mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
mesh_child = bpy.data.meshes.new(TestLibraryOverrides.MESH_LIBRARY_CHILD)
|
|
obj_child = bpy.data.objects.new(TestLibraryOverrides.OBJECT_LIBRARY_CHILD, object_data=mesh_child)
|
|
obj_child.parent = obj
|
|
bpy.context.collection.objects.link(obj_child)
|
|
|
|
mesh = bpy.data.meshes.new(TestLibraryOverrides.MESH_LIBRARY_PERMISSIVE)
|
|
obj = bpy.data.objects.new(TestLibraryOverrides.OBJECT_LIBRARY_PERMISSIVE, object_data=mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
|
|
bpy.ops.wm.save_as_mainfile(filepath=str(self.output_path), check_existing=False, compress=False)
|
|
|
|
def test_link_and_override_property(self):
|
|
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
|
|
bpy.data.orphans_purge()
|
|
|
|
link_dir = self.output_path / "Object"
|
|
bpy.ops.wm.link(directory=str(link_dir), filename=TestLibraryOverrides.OBJECT_LIBRARY_PARENT)
|
|
|
|
obj = bpy.data.objects[TestLibraryOverrides.OBJECT_LIBRARY_PARENT]
|
|
self.assertIsNone(obj.override_library)
|
|
local_id = obj.override_create()
|
|
self.assertIsNotNone(local_id.override_library)
|
|
self.assertIsNone(local_id.data.override_library)
|
|
self.assertEqual(len(local_id.override_library.properties), 0)
|
|
|
|
# #### Generate an override property & operation automatically by editing the local override data.
|
|
local_id.location.y = 1.0
|
|
local_id.override_library.operations_update()
|
|
self.assertEqual(len(local_id.override_library.properties), 1)
|
|
override_prop = local_id.override_library.properties[0]
|
|
self.assertEqual(override_prop.rna_path, "location")
|
|
self.assertEqual(len(override_prop.operations), 1)
|
|
override_operation = override_prop.operations[0]
|
|
self.assertEqual(override_operation.operation, 'REPLACE')
|
|
# Setting location.y overrode all elements in the location array. -1 is a wildcard.
|
|
self.assertEqual(override_operation.subitem_local_index, -1)
|
|
|
|
# #### Reset the override to its linked reference data.
|
|
local_id.override_library.reset()
|
|
self.assertEqual(len(local_id.override_library.properties), 0)
|
|
self.assertEqual(local_id.location, local_id.override_library.reference.location)
|
|
|
|
# #### Generate an override property & operation manually using the API.
|
|
override_property = local_id.override_library.properties.add(rna_path="location")
|
|
override_property.operations.add(operation='REPLACE')
|
|
|
|
self.assertEqual(len(local_id.override_library.properties), 1)
|
|
override_prop = local_id.override_library.properties[0]
|
|
self.assertEqual(override_prop.rna_path, "location")
|
|
self.assertEqual(len(override_prop.operations), 1)
|
|
override_operation = override_prop.operations[0]
|
|
self.assertEqual(override_operation.operation, 'REPLACE')
|
|
# Setting location.y overrode all elements in the location array. -1 is a wildcard.
|
|
self.assertEqual(override_operation.subitem_local_index, -1)
|
|
|
|
override_property = local_id.override_library.properties[0]
|
|
override_property.operations.remove(override_property.operations[0])
|
|
local_id.override_library.properties.remove(override_property)
|
|
|
|
self.assertEqual(len(local_id.override_library.properties), 0)
|
|
|
|
# #### Delete the override.
|
|
local_id_name = local_id.name
|
|
self.assertEqual(bpy.data.objects.get((local_id_name, None), None), local_id)
|
|
local_id.override_library.destroy()
|
|
self.assertIsNone(bpy.data.objects.get((local_id_name, None), None))
|
|
|
|
def test_link_permissive(self):
|
|
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
|
|
bpy.data.orphans_purge()
|
|
|
|
link_dir = self.output_path / "Object"
|
|
bpy.ops.wm.link(directory=str(link_dir), filename=TestLibraryOverrides.OBJECT_LIBRARY_PERMISSIVE)
|
|
|
|
obj = bpy.data.objects[TestLibraryOverrides.OBJECT_LIBRARY_PERMISSIVE]
|
|
self.assertIsNone(obj.override_library)
|
|
local_id = obj.override_create()
|
|
self.assertIsNotNone(local_id.override_library)
|
|
self.assertIsNone(local_id.data.override_library)
|
|
self.assertEqual(len(local_id.override_library.properties), 0)
|
|
local_id.location.y = 1.0
|
|
self.assertEqual(local_id.location.y, 1.0)
|
|
|
|
local_id.override_library.operations_update()
|
|
self.assertEqual(local_id.location.y, 1.0)
|
|
|
|
self.assertEqual(len(local_id.override_library.properties), 1)
|
|
override_prop = local_id.override_library.properties[0]
|
|
self.assertEqual(override_prop.rna_path, "location")
|
|
self.assertEqual(len(override_prop.operations), 1)
|
|
override_operation = override_prop.operations[0]
|
|
self.assertEqual(override_operation.operation, 'REPLACE')
|
|
self.assertEqual(override_operation.subitem_local_index, -1)
|
|
|
|
|
|
class TestLibraryOverridesComplex(TestHelper):
|
|
# Test resync, recursive resync, overrides of overrides, ID names collision handling, and multiple overrides.
|
|
|
|
DATA_NAME_CONTAINER = "LibCollection"
|
|
DATA_NAME_SUBCONTAINER_0 = "LibSubCollection_0"
|
|
DATA_NAME_SUBCONTAINER_1 = "LibSubCollection_1"
|
|
DATA_NAME_SUBCONTAINER_2 = "LibSubCollection_2"
|
|
DATA_NAME_RIGGED = "LibRigged"
|
|
DATA_NAME_RIG = "LibRig"
|
|
DATA_NAME_CONTROLLER_1 = "LibController1"
|
|
DATA_NAME_CONTROLLER_2 = "LibController2"
|
|
DATA_NAME_SAMENAME_CONTAINER = "LibCube"
|
|
DATA_NAME_SAMENAME_0 = "LibCube"
|
|
DATA_NAME_SAMENAME_1 = "LibCube.001"
|
|
DATA_NAME_SAMENAME_2 = "LibCube.002"
|
|
DATA_NAME_SAMENAME_3 = "LibCube.003"
|
|
|
|
def __init__(self, args):
|
|
super().__init__(args)
|
|
|
|
output_dir = pathlib.Path(self.args.output_dir)
|
|
self.ensure_path(str(output_dir))
|
|
self.lib_output_path = output_dir / "blendlib_overrides_lib.blend"
|
|
self.test_output_path = output_dir / "blendlib_overrides_test.blend"
|
|
self.test_output_path_recursive = output_dir / "blendlib_overrides_test_recursive.blend"
|
|
|
|
def reset(self):
|
|
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
|
|
|
|
def init_lib_data(self, custom_cb=None):
|
|
self.reset()
|
|
|
|
collection_container = bpy.data.collections.new(self.__class__.DATA_NAME_CONTAINER)
|
|
bpy.context.collection.children.link(collection_container)
|
|
|
|
mesh = bpy.data.meshes.new(self.__class__.DATA_NAME_RIGGED)
|
|
obj_child = bpy.data.objects.new(self.__class__.DATA_NAME_RIGGED, object_data=mesh)
|
|
collection_container.objects.link(obj_child)
|
|
armature = bpy.data.armatures.new(self.__class__.DATA_NAME_RIG)
|
|
obj_armature = bpy.data.objects.new(self.__class__.DATA_NAME_RIG, object_data=armature)
|
|
obj_child.parent = obj_armature
|
|
collection_container.objects.link(obj_armature)
|
|
|
|
obj_child_modifier = obj_child.modifiers.new("", 'ARMATURE')
|
|
obj_child_modifier.object = obj_armature
|
|
|
|
obj_ctrl1 = bpy.data.objects.new(self.__class__.DATA_NAME_CONTROLLER_1, object_data=None)
|
|
collection_container.objects.link(obj_ctrl1)
|
|
|
|
obj_armature_constraint = obj_armature.constraints.new('COPY_LOCATION')
|
|
obj_armature_constraint.target = obj_ctrl1
|
|
|
|
collection_sub = bpy.data.collections.new(self.__class__.DATA_NAME_CONTROLLER_2)
|
|
collection_container.children.link(collection_sub)
|
|
obj_ctrl2 = bpy.data.objects.new(self.__class__.DATA_NAME_CONTROLLER_2, object_data=None)
|
|
collection_sub.objects.link(obj_ctrl2)
|
|
|
|
collection_sub = bpy.data.collections.new(self.__class__.DATA_NAME_SAMENAME_CONTAINER)
|
|
collection_container.children.link(collection_sub)
|
|
# 'Samename' objects are purposedly not added to the collection here.
|
|
|
|
# Sub-container collections are empty by default.
|
|
collection_subcontainer_0 = bpy.data.collections.new(self.__class__.DATA_NAME_SUBCONTAINER_0)
|
|
collection_container.children.link(collection_subcontainer_0)
|
|
collection_subcontainer_1 = bpy.data.collections.new(self.__class__.DATA_NAME_SUBCONTAINER_1)
|
|
collection_container.children.link(collection_subcontainer_1)
|
|
collection_subcontainer_2 = bpy.data.collections.new(self.__class__.DATA_NAME_SUBCONTAINER_2)
|
|
collection_container.children.link(collection_subcontainer_2)
|
|
|
|
if custom_cb is not None:
|
|
custom_cb(self)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.lib_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
def edit_lib_data(self, custom_cb):
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.lib_output_path))
|
|
custom_cb(self)
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.lib_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
def link_lib_data(self, num_collections, num_objects, num_meshes, num_armatures):
|
|
link_dir = self.lib_output_path / "Collection"
|
|
bpy.ops.wm.link(
|
|
directory=str(link_dir),
|
|
filename=self.__class__.DATA_NAME_CONTAINER,
|
|
instance_collections=False,
|
|
relative_path=False,
|
|
)
|
|
|
|
linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
|
|
self.assertIsNotNone(linked_collection_container.library)
|
|
self.assertIsNone(linked_collection_container.override_library)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, num_collections[0], num_collections[1])
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, num_objects[0], num_objects[1])
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, num_meshes[0], num_meshes[1])
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, num_armatures[0], num_armatures[1])
|
|
|
|
return linked_collection_container
|
|
|
|
def link_liboverride_data(self, num_collections, num_objects, num_meshes, num_armatures):
|
|
link_dir = self.test_output_path / "Collection"
|
|
bpy.ops.wm.link(
|
|
directory=str(link_dir),
|
|
filename=self.__class__.DATA_NAME_CONTAINER,
|
|
instance_collections=False,
|
|
relative_path=False,
|
|
)
|
|
|
|
linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str(
|
|
self.test_output_path)]
|
|
self.assertIsNotNone(linked_collection_container.library)
|
|
self.assertIsNotNone(linked_collection_container.override_library)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, num_collections[0], num_collections[1])
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, num_objects[0], num_objects[1])
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, num_meshes[0], num_meshes[1])
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, num_armatures[0], num_armatures[1])
|
|
|
|
self.liboverride_hierarchy_validate(linked_collection_container)
|
|
|
|
return linked_collection_container
|
|
|
|
def liboverride_hierarchy_validate(self, root_collection):
|
|
def liboverride_systemoverrideonly_hierarchy_validate(id_, id_root):
|
|
if not id_.override_library:
|
|
return
|
|
self.assertEqual(id_.override_library.hierarchy_root, id_root)
|
|
for op in id_.override_library.properties:
|
|
for opop in op.operations:
|
|
self.assertIn('IDPOINTER_MATCH_REFERENCE', opop.flag)
|
|
|
|
for coll_ in root_collection.children_recursive:
|
|
liboverride_systemoverrideonly_hierarchy_validate(coll_, root_collection)
|
|
if coll_.override_library:
|
|
for op in coll_.override_library.properties:
|
|
for opop in op.operations:
|
|
self.assertIn('IDPOINTER_ITEM_USE_ID', opop.flag)
|
|
print(
|
|
coll_,
|
|
opop.flag,
|
|
opop.subitem_reference_name,
|
|
opop.subitem_reference_id,
|
|
opop.subitem_local_name,
|
|
opop.subitem_local_id)
|
|
self.assertIsNotNone(opop.subitem_reference_id.library)
|
|
self.assertTrue(opop.subitem_local_id.library is None if coll_.library is None
|
|
else opop.subitem_local_id.library is not None)
|
|
for ob_ in root_collection.all_objects:
|
|
liboverride_systemoverrideonly_hierarchy_validate(ob_, root_collection)
|
|
|
|
# IDs container, number of local IDs, of linked IDs, and number of liboverrides (local, linked).
|
|
def local_and_linked_ids_numbers_validate(
|
|
self, ids, local_num, linked_num, linked_missing_num=..., liboverride_num=()):
|
|
self.assertEqual(len(ids), local_num + linked_num)
|
|
self.assertFalse(any(id_.library is not None for id_ in ids[:local_num]))
|
|
self.assertFalse(any(id_.library is None for id_ in ids[local_num:]))
|
|
if liboverride_num:
|
|
self.assertEqual(len([id_ for id_ in ids[:local_num] if id_.override_library is not None]),
|
|
liboverride_num[0])
|
|
self.assertEqual(len([id_ for id_ in ids[local_num:] if id_.override_library is not None]),
|
|
liboverride_num[1])
|
|
if linked_missing_num is not ...:
|
|
self.assertEqual(len([id_ for id_ in ids[local_num:] if id_.is_missing]), linked_missing_num)
|
|
|
|
def test_link_and_override_resync(self):
|
|
self.init_lib_data()
|
|
self.reset()
|
|
|
|
# NOTE: All counts below are in the form `local_ids, linked_ids`.
|
|
linked_collection_container = self.link_lib_data(
|
|
num_collections=(0, 6),
|
|
num_objects=(0, 4),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
override_collection_container = linked_collection_container.override_hierarchy_create(
|
|
bpy.context.scene,
|
|
bpy.context.view_layer,
|
|
)
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides (except for empty collection),
|
|
# but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 2, 6, liboverride_num=(2, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 4, 4, liboverride_num=(4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Create linked liboverrides file (for recursive resync).
|
|
self.reset()
|
|
|
|
self.link_liboverride_data(
|
|
num_collections=(0, 8),
|
|
num_objects=(0, 8),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path_recursive),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Re-open the lib file, and change its ID relationships.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.lib_output_path))
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_armature_constraint = obj_armature.constraints[0]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
obj_armature_constraint.target = obj_ctrl2
|
|
|
|
bpy.ops.wm.save_as_mainfile(filepath=str(self.lib_output_path), check_existing=False, compress=False)
|
|
|
|
# Re-open the main file, and check that automatic resync did its work correctly, remapping the target of the
|
|
# armature constraint to controller 2, without creating unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 2, 6, liboverride_num=(2, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 4, 4, liboverride_num=(4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
self.assertIsNone(obj_armature.library)
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNone(obj_ctrl2.library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
# Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly,
|
|
# remapping the target of the linked liboverride armature constraint to controller 2, without creating
|
|
# unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path_recursive))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str(
|
|
self.test_output_path)]
|
|
self.assertIsNotNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
test_output_path_lib = override_collection_container.library
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 0, 2 + 6, liboverride_num=(0, 2))
|
|
self.assertTrue(all((id_.override_library is not None)
|
|
for id_ in bpy.data.collections if id_.library == test_output_path_lib))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 0, 4 + 4, liboverride_num=(0, 4))
|
|
self.assertTrue(all((id_.override_library is not None)
|
|
for id_ in bpy.data.objects if id_.library == test_output_path_lib))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG, str(self.test_output_path)]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2, str(self.test_output_path)]
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
def test_link_and_override_multiple(self):
|
|
self.init_lib_data()
|
|
self.reset()
|
|
|
|
# NOTE: All counts below are in the form `local_ids, linked_ids`.
|
|
linked_collection_container = self.link_lib_data(
|
|
num_collections=(0, 6),
|
|
num_objects=(0, 4),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
override_collection_containers = [linked_collection_container.override_hierarchy_create(
|
|
bpy.context.scene,
|
|
bpy.context.view_layer,
|
|
) for i in range(3)]
|
|
for override_container in override_collection_containers:
|
|
self.assertIsNone(override_container.library)
|
|
self.assertIsNotNone(override_container.override_library)
|
|
self.liboverride_hierarchy_validate(override_container)
|
|
|
|
# Objects and collections are duplicated as overrides (except for empty collection),
|
|
# but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 3 * 2, 6, liboverride_num=(3 * 2, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 3 * 4, 4, liboverride_num=(3 * 4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Create linked liboverrides file (for recursive resync).
|
|
self.reset()
|
|
|
|
self.link_liboverride_data(
|
|
num_collections=(0, 8),
|
|
num_objects=(0, 8),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path_recursive),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Change the lib's ID relationships.
|
|
def edit_lib_cb(self):
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_armature_constraint = obj_armature.constraints[0]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
obj_armature_constraint.target = obj_ctrl2
|
|
self.edit_lib_data(edit_lib_cb)
|
|
|
|
# Re-open the main file, and check that automatic resync did its work correctly, remapping the target of the
|
|
# armature constraint to controller 2, without creating unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 3 * 2, 6, liboverride_num=(3 * 2, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 3 * 4, 4, liboverride_num=(3 * 4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
self.assertIsNotNone(obj_armature.library is None and obj_armature.override_library)
|
|
self.assertIsNotNone(obj_ctrl2.library is None and obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
override_collection_containers = [
|
|
bpy.data.collections[self.__class__.DATA_NAME_CONTAINER],
|
|
bpy.data.collections[self.__class__.DATA_NAME_CONTAINER + ".001"],
|
|
bpy.data.collections[self.__class__.DATA_NAME_CONTAINER + ".002"],
|
|
]
|
|
for override_container in override_collection_containers:
|
|
self.assertIsNone(override_container.library)
|
|
self.assertIsNotNone(override_container.override_library)
|
|
self.liboverride_hierarchy_validate(override_container)
|
|
|
|
# Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly,
|
|
# remapping the target of the linked liboverride armature constraint to controller 2, without creating
|
|
# unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path_recursive))
|
|
|
|
linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str(
|
|
self.test_output_path)]
|
|
self.assertIsNotNone(linked_collection_container.library)
|
|
self.assertIsNotNone(linked_collection_container.override_library)
|
|
test_output_path_lib = linked_collection_container.library
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 0, 2 + 6, liboverride_num=(0, 2))
|
|
self.assertTrue(all((id_.override_library is not None)
|
|
for id_ in bpy.data.collections if id_.library == test_output_path_lib))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 0, 4 + 4, liboverride_num=(0, 4))
|
|
self.assertTrue(all((id_.override_library is not None)
|
|
for id_ in bpy.data.objects if id_.library == test_output_path_lib))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG, str(self.test_output_path)]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2, str(self.test_output_path)]
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(linked_collection_container)
|
|
|
|
def test_link_and_override_of_override(self):
|
|
def init_lib_cb(self):
|
|
collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
# Sub-container collections become a non-empty, multi-level hierarchy.
|
|
collection_subcontainer_0 = bpy.data.collections[self.__class__.DATA_NAME_SUBCONTAINER_0]
|
|
collection_subcontainer_1 = bpy.data.collections[self.__class__.DATA_NAME_SUBCONTAINER_1]
|
|
collection_subcontainer_2 = bpy.data.collections[self.__class__.DATA_NAME_SUBCONTAINER_2]
|
|
collection_container.children.unlink(collection_subcontainer_2)
|
|
collection_subcontainer_0.children.link(collection_subcontainer_2)
|
|
|
|
obj_ctrl1 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_1]
|
|
collection_container.objects.unlink(obj_ctrl1)
|
|
collection_subcontainer_2.objects.link(obj_ctrl1)
|
|
|
|
# Now obj_ctrl2 is both in its (original) sub-container conllection, and in collection_subcontainer_1.
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
collection_subcontainer_1.objects.link(obj_ctrl2)
|
|
|
|
self.init_lib_data(init_lib_cb)
|
|
self.reset()
|
|
|
|
# NOTE: All counts below are in the form `local_ids, linked_ids`.
|
|
linked_collection_container = self.link_lib_data(
|
|
num_collections=(0, 6),
|
|
num_objects=(0, 4),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
override_collection_container = linked_collection_container.override_hierarchy_create(
|
|
bpy.context.scene,
|
|
bpy.context.view_layer,
|
|
)
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
|
|
# Objects and collections are duplicated as overrides (except for empty collection),
|
|
# but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 5, 6, liboverride_num=(5, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 4, 4, liboverride_num=(4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Create liboverrides of liboverrides file.
|
|
self.reset()
|
|
|
|
linked_collection_container = self.link_liboverride_data(
|
|
num_collections=(0, 11),
|
|
num_objects=(0, 8),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
override_collection_container = linked_collection_container.override_hierarchy_create(
|
|
bpy.context.scene,
|
|
bpy.context.view_layer,
|
|
)
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
|
|
# Objects and collections are duplicated as overrides (except for empty collection),
|
|
# but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 5, 5 + 6, liboverride_num=(5, 5))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 4, 4 + 4, liboverride_num=(4, 4))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path_recursive),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Edit the lib file, change its ID relationships.
|
|
def edit_lib_cb(self):
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_armature_constraint = obj_armature.constraints[0]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
obj_armature_constraint.target = obj_ctrl2
|
|
|
|
# Also modify sub-container collection hierarchies.
|
|
collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
# Sub-container 1 is moved from collection_container to sub-container 0.
|
|
collection_subcontainer_0 = bpy.data.collections[self.__class__.DATA_NAME_SUBCONTAINER_0]
|
|
collection_subcontainer_1 = bpy.data.collections[self.__class__.DATA_NAME_SUBCONTAINER_1]
|
|
collection_container.children.unlink(collection_subcontainer_1)
|
|
collection_subcontainer_0.children.link(collection_subcontainer_1)
|
|
|
|
self.edit_lib_data(edit_lib_cb)
|
|
self.reset()
|
|
|
|
# Re-open the main file, and check that automatic resync did its work correctly, remapping the target of the
|
|
# armature constraint to controller 2, without creating unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 5, 6, liboverride_num=(5, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 4, 4, liboverride_num=(4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
self.assertIsNone(obj_armature.library)
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNone(obj_ctrl2.library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly,
|
|
# remapping the target of the linked liboverride armature constraint to controller 2, without creating
|
|
# unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path_recursive))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 5, 5 + 6, liboverride_num=(5, 5))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 4, 4 + 4, liboverride_num=(4, 4))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path_recursive),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Clear the library from all of its data.
|
|
self.edit_lib_data(lambda s: s.reset())
|
|
self.reset()
|
|
|
|
# Re-open the main file, and check that automatic resync did its work correctly, preserving as much as possible
|
|
# from the missing linked data.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(
|
|
bpy.data.collections, 5, 6, linked_missing_num=6, liboverride_num=(5, 0))
|
|
self.local_and_linked_ids_numbers_validate(
|
|
bpy.data.objects, 4, 4, linked_missing_num=4, liboverride_num=(4, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
self.assertIsNone(obj_armature.library)
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNone(obj_ctrl2.library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
print(obj_armature.constraints[0].target)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
# Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly,
|
|
# remapping the target of the linked liboverride armature constraint to controller 2, without creating
|
|
# unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path_recursive))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(
|
|
bpy.data.collections, 5, 5 + 6, linked_missing_num=6, liboverride_num=(5, 5))
|
|
self.local_and_linked_ids_numbers_validate(
|
|
bpy.data.objects, 4, 4 + 4, linked_missing_num=4, liboverride_num=(4, 4))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
obj_armature = bpy.data.objects[self.__class__.DATA_NAME_RIG]
|
|
obj_ctrl2 = bpy.data.objects[self.__class__.DATA_NAME_CONTROLLER_2]
|
|
self.assertIsNotNone(obj_armature.override_library)
|
|
self.assertIsNotNone(obj_ctrl2.override_library)
|
|
self.assertEqual(obj_armature.constraints[0].target, obj_ctrl2)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
def test_link_and_override_idnames_conflict(self):
|
|
def init_lib_cb(self):
|
|
# Add some 'samename' objects to the library.
|
|
collection_sub = bpy.data.collections[self.__class__.DATA_NAME_SAMENAME_CONTAINER]
|
|
obj_samename_0 = bpy.data.objects.new(self.__class__.DATA_NAME_SAMENAME_0, object_data=None)
|
|
collection_sub.objects.link(obj_samename_0)
|
|
obj_samename_3 = bpy.data.objects.new(self.__class__.DATA_NAME_SAMENAME_3, object_data=None)
|
|
collection_sub.objects.link(obj_samename_3)
|
|
self.init_lib_data(init_lib_cb)
|
|
self.reset()
|
|
|
|
# NOTE: All counts below are in the form `local_ids, linked_ids`.
|
|
linked_collection_container = self.link_lib_data(
|
|
num_collections=(0, 6),
|
|
num_objects=(0, 6),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
override_collection_containers = [linked_collection_container.override_hierarchy_create(
|
|
bpy.context.scene,
|
|
bpy.context.view_layer,
|
|
) for i in range(3)]
|
|
for override_container in override_collection_containers:
|
|
self.assertIsNone(override_container.library)
|
|
self.assertIsNotNone(override_container.override_library)
|
|
self.liboverride_hierarchy_validate(override_container)
|
|
|
|
# Objects and collections are duplicated as overrides (except for empty collection),
|
|
# but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 3 * 3, 6, liboverride_num=(3 * 3, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 3 * 6, 6, liboverride_num=(3 * 6, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
self.assertEqual(
|
|
bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_0].override_library.reference.name,
|
|
self.__class__.DATA_NAME_SAMENAME_0)
|
|
self.assertEqual(
|
|
bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_3].override_library.reference.name,
|
|
self.__class__.DATA_NAME_SAMENAME_3)
|
|
# These names will be used by the second created liboverride, due to how
|
|
# naming is currently handled when original name is already used.
|
|
self.assertEqual(
|
|
bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_1].override_library.reference.name,
|
|
self.__class__.DATA_NAME_SAMENAME_0)
|
|
self.assertEqual(
|
|
bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_2].override_library.reference.name,
|
|
self.__class__.DATA_NAME_SAMENAME_3)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Create liboverrides of liboverrides file.
|
|
self.reset()
|
|
|
|
linked_collection_container = self.link_liboverride_data(
|
|
num_collections=(0, 9),
|
|
num_objects=(0, 12),
|
|
num_meshes=(0, 1),
|
|
num_armatures=(0, 1))
|
|
|
|
override_collection_container = linked_collection_container.override_hierarchy_create(
|
|
bpy.context.scene,
|
|
bpy.context.view_layer,
|
|
)
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
|
|
# Objects and collections are duplicated as overrides (except for empty collection),
|
|
# but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 3, 3 + 6, liboverride_num=(3, 3))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 6, 6 + 6, liboverride_num=(6, 6))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
self.liboverride_hierarchy_validate(override_collection_container)
|
|
|
|
bpy.ops.wm.save_as_mainfile(
|
|
filepath=str(self.test_output_path_recursive),
|
|
check_existing=False,
|
|
compress=False,
|
|
relative_remap=False,
|
|
)
|
|
|
|
# Modify the names of 'samename' objects in the library to generate ID name collisions.
|
|
def edit_lib_cb(self):
|
|
obj_samename_0 = bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_0]
|
|
obj_samename_3 = bpy.data.objects[self.__class__.DATA_NAME_SAMENAME_3]
|
|
obj_samename_0.name = self.__class__.DATA_NAME_SAMENAME_2
|
|
obj_samename_3.name = self.__class__.DATA_NAME_SAMENAME_1
|
|
self.edit_lib_data(edit_lib_cb)
|
|
|
|
# Re-open the main file, and check that automatic resync did its work correctly, remapping the target of the
|
|
# armature constraint to controller 2, without creating unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path))
|
|
|
|
override_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER]
|
|
self.assertIsNone(override_collection_container.library)
|
|
self.assertIsNotNone(override_collection_container.override_library)
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 3 * 3, 6, liboverride_num=(3 * 3, 0))
|
|
# Note that the 'missing' renamed objects from the library are now cleared as part of the resync process.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 3 * 6, 6, liboverride_num=(3 * 6, 0))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
override_collection_containers = [
|
|
bpy.data.collections[self.__class__.DATA_NAME_CONTAINER],
|
|
bpy.data.collections[self.__class__.DATA_NAME_CONTAINER + ".001"],
|
|
bpy.data.collections[self.__class__.DATA_NAME_CONTAINER + ".002"],
|
|
]
|
|
for override_container in override_collection_containers:
|
|
self.assertIsNone(override_container.library)
|
|
self.assertIsNotNone(override_container.override_library)
|
|
self.liboverride_hierarchy_validate(override_container)
|
|
|
|
# Re-open the 'recursive resync' file, and check that automatic recursive resync did its work correctly,
|
|
# remapping the target of the linked liboverride armature constraint to controller 2, without creating
|
|
# unexpected garbage IDs along the line.
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_output_path_recursive))
|
|
|
|
linked_collection_container = bpy.data.collections[self.__class__.DATA_NAME_CONTAINER, str(
|
|
self.test_output_path)]
|
|
self.assertIsNotNone(linked_collection_container.library)
|
|
self.assertIsNotNone(linked_collection_container.override_library)
|
|
|
|
test_output_path_lib = linked_collection_container.library
|
|
# Objects and collections are duplicated as overrides, but meshes and armatures remain only linked data.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.collections, 3, 3 + 6, liboverride_num=(3, 3))
|
|
# Note that the 'missing' renamed objects from the library are now cleared as part of the resync process.
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.objects, 6, 6 + 6, liboverride_num=(6, 6))
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.meshes, 0, 1)
|
|
self.local_and_linked_ids_numbers_validate(bpy.data.armatures, 0, 1)
|
|
|
|
self.liboverride_hierarchy_validate(linked_collection_container)
|
|
|
|
|
|
class TestLibraryOverridesFromProxies(TestHelper):
|
|
# Very basic test, could be improved/extended.
|
|
# NOTE: Tests way more than only liboverride proxy conversion actually, since this is a fairly old .blend file.
|
|
|
|
MAIN_BLEND_FILE = "library_test_scene.blend"
|
|
|
|
def __init__(self, args):
|
|
super().__init__(args)
|
|
|
|
self.test_dir = pathlib.Path(self.args.test_dir)
|
|
self.assertTrue(self.test_dir.exists(),
|
|
msg='Test dir {0} should exist'.format(self.test_dir))
|
|
|
|
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
|
|
|
|
def test_open_linked_proxy_file(self):
|
|
bpy.ops.wm.open_mainfile(filepath=str(self.test_dir / self.MAIN_BLEND_FILE))
|
|
|
|
# Check stability of 'same name' fixing for IDs.
|
|
direct_linked_A = bpy.data.libraries["lib.002"]
|
|
self.assertEqual(direct_linked_A.filepath, os.path.join("//libraries", "direct_linked_A.blend"))
|
|
|
|
self.assertEqual(bpy.data.objects['HairCubeArmatureGroup_proxy'].library, direct_linked_A)
|
|
self.assertIsNotNone(bpy.data.objects['HairCubeArmatureGroup_proxy'].override_library)
|
|
|
|
|
|
TESTS = (
|
|
TestLibraryOverrides,
|
|
TestLibraryOverridesComplex,
|
|
TestLibraryOverridesFromProxies,
|
|
)
|
|
|
|
|
|
def argparse_create():
|
|
import argparse
|
|
|
|
# When --help or no args are given, print this help
|
|
description = "Test library overrides of blend file."
|
|
parser = argparse.ArgumentParser(description=description)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
dest="output_dir",
|
|
default=".",
|
|
help="Where to output temp saved blendfiles",
|
|
required=False,
|
|
)
|
|
parser.add_argument(
|
|
"--test-dir",
|
|
dest="test_dir",
|
|
default=".",
|
|
help="Where are the test blendfiles",
|
|
required=False,
|
|
)
|
|
|
|
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()
|