From f052b18a65520ee9178326a8219a36c8114b60f3 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Fri, 11 Aug 2023 12:28:54 +0200 Subject: [PATCH] Always clear 'fake user' flag from linked data. Using this flag from linked data is always a double-edge sword, in one end some user have been relying on it to keep around data that is not really used as ID (like e.g. text data-blocks, node trees, see e.g. #103687, #105687). On the other end, it often causes over-keeping of linked data reference in production files. From now on, when an unused linked data is to be kept around, users should create an ID property to reference it. Implements #106321. Pull Request: https://projects.blender.org/blender/blender/pulls/111042 --- source/blender/blenloader/intern/readfile.cc | 4 +++ source/blender/blenloader/intern/writefile.cc | 10 ------ tests/python/bl_blendfile_io.py | 15 ++++---- tests/python/bl_blendfile_liblink.py | 34 +++++++++---------- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 2b2eecae201..d9fa0287f4a 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -2061,6 +2061,10 @@ static void direct_link_id_common( } id->lib = current_library; + if (id->lib) { + /* Always fully clear fake user flag for linked data. */ + id->flag &= ~LIB_FAKEUSER; + } id->us = ID_FAKE_USERS(id); id->icon_id = 0; id->newid = nullptr; /* Needed because .blend may have been saved with crap value here... */ diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index 34e44d58ffa..a7d7209d85f 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -1225,16 +1225,6 @@ static bool write_file_handle(Main *mainvar, * asap afterward. */ id_lib_extern(id_iter); } - else if (ID_FAKE_USERS(id_iter) > 0 && id_iter->asset_data == nullptr) { - /* Even though fake user is not directly editable by the user on linked data, it is a - * common 'work-around' to set it in library files on data-blocks that need to be linked - * but typically do not have an actual real user (e.g. texts, etc.). - * See e.g. #105687 and #103867. - * - * Would be good to find a better solution, but for now consider these as directly linked - * as well. */ - id_lib_extern(id_iter); - } else { id_iter->tag |= LIB_TAG_INDIRECT; id_iter->tag &= ~LIB_TAG_EXTERN; diff --git a/tests/python/bl_blendfile_io.py b/tests/python/bl_blendfile_io.py index 50cd058fca8..418a49a069f 100644 --- a/tests/python/bl_blendfile_io.py +++ b/tests/python/bl_blendfile_io.py @@ -115,10 +115,11 @@ class TestIdRuntimeTag(TestHelper): assert linked_material.is_library_indirect is False link_dir = os.path.join(output_lib_path, "Mesh") - bpy.ops.wm.link(directory=link_dir, filename="LibMesh") + bpy.ops.wm.link(directory=link_dir, filename="LibMesh", instance_object_data=False) linked_mesh = bpy.data.meshes['LibMesh'] assert linked_mesh.is_library_indirect is False + assert linked_mesh.use_fake_user is False obj.data = linked_mesh obj.material_slots[0].link = 'OBJECT' @@ -131,17 +132,15 @@ class TestIdRuntimeTag(TestHelper): # so writing .blend file will have properly reset its tag to indirectly linked data. assert linked_material.is_library_indirect - # Only usage of this linked mesh is a runtime ID (object), but it is flagged as 'fake user' in its library, - # so writing .blend file will have kept its tag to directly linked data. - assert not linked_mesh.is_library_indirect + # Only usage of this linked mesh is a runtime ID (object), + # so writing .blend file will have properly reset its tag to indirectly linked data. + assert linked_mesh.is_library_indirect bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) assert 'Cube' not in bpy.data.objects - assert 'LibMaterial' in bpy.data.materials # Pulled-in by the linked mesh. - linked_mesh = bpy.data.meshes['LibMesh'] - assert linked_mesh.use_fake_user is True - assert linked_mesh.is_library_indirect is False + assert 'LibMaterial' not in bpy.data.materials + assert 'libMesh' not in bpy.data.meshes TESTS = ( diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index 626abc27c95..6f058155c33 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -214,9 +214,9 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper): material = bpy.data.materials[0] assert material.library is not None - assert material.use_fake_user is True - assert material.users == 2 # Fake user is not cleared when linking. - assert material.is_library_indirect + assert material.use_fake_user is False # Fake user is cleared when linking. + assert material.users == 1 + assert material.is_library_indirect is True assert mesh.library is not None assert mesh.use_fake_user is False @@ -229,29 +229,28 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper): coll.objects.link(ob) bpy.context.scene.collection.children.link(coll) - assert material.users == 2 - assert material.is_library_indirect + assert material.users == 1 + assert material.is_library_indirect is True assert mesh.users == 1 assert mesh.is_library_indirect is False ob.material_slots[0].link = 'OBJECT' ob.material_slots[0].material = material - assert material.users == 3 + assert material.users == 2 assert material.is_library_indirect is False ob.material_slots[0].material = None - assert material.users == 2 + assert material.users == 1 # This is not properly updated whene removing a local user of linked data. assert material.is_library_indirect is False output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile")) bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False) - assert material.users == 2 - # Currently linked data with 'fake user' set are considered as directly linked data. - assert not material.is_library_indirect + assert material.users == 1 + assert material.is_library_indirect is True bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) @@ -264,10 +263,9 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper): material = bpy.data.materials[0] assert material.library is not None - assert material.use_fake_user is True - assert material.users == 2 # Fake user is not cleared when linking. - # Currently linked data with 'fake user' set are considered as directly linked data. - assert not material.is_library_indirect + assert material.use_fake_user is False # Fake user is cleared when linking. + assert material.users == 1 + assert material.is_library_indirect is True assert mesh.library is not None assert mesh.use_fake_user is False @@ -293,7 +291,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert len(bpy.data.materials) == 1 assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking. + assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. assert len(bpy.data.meshes) == 1 assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].use_fake_user is False @@ -310,7 +308,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert len(bpy.data.materials) == 1 assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking. + assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. assert len(bpy.data.meshes) == 1 assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].use_fake_user is False @@ -328,7 +326,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert len(bpy.data.materials) == 1 assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking. + assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. assert len(bpy.data.meshes) == 1 assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].use_fake_user is True @@ -345,7 +343,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert len(bpy.data.materials) == 1 assert bpy.data.materials[0].library is not None - assert bpy.data.materials[0].users == 2 # Fake user is not cleared when linking. + assert bpy.data.materials[0].users == 1 # Fake user is cleared when linking. assert len(bpy.data.meshes) == 1 assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].users == 1