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
This commit is contained in:
Bastien Montagne 2023-08-11 12:28:54 +02:00 committed by Gitea
parent 7f65080ab4
commit f052b18a65
4 changed files with 27 additions and 36 deletions

View File

@ -2061,6 +2061,10 @@ static void direct_link_id_common(
} }
id->lib = current_library; 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->us = ID_FAKE_USERS(id);
id->icon_id = 0; id->icon_id = 0;
id->newid = nullptr; /* Needed because .blend may have been saved with crap value here... */ id->newid = nullptr; /* Needed because .blend may have been saved with crap value here... */

View File

@ -1225,16 +1225,6 @@ static bool write_file_handle(Main *mainvar,
* asap afterward. */ * asap afterward. */
id_lib_extern(id_iter); 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 { else {
id_iter->tag |= LIB_TAG_INDIRECT; id_iter->tag |= LIB_TAG_INDIRECT;
id_iter->tag &= ~LIB_TAG_EXTERN; id_iter->tag &= ~LIB_TAG_EXTERN;

View File

@ -115,10 +115,11 @@ class TestIdRuntimeTag(TestHelper):
assert linked_material.is_library_indirect is False assert linked_material.is_library_indirect is False
link_dir = os.path.join(output_lib_path, "Mesh") 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'] linked_mesh = bpy.data.meshes['LibMesh']
assert linked_mesh.is_library_indirect is False assert linked_mesh.is_library_indirect is False
assert linked_mesh.use_fake_user is False
obj.data = linked_mesh obj.data = linked_mesh
obj.material_slots[0].link = 'OBJECT' 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. # so writing .blend file will have properly reset its tag to indirectly linked data.
assert linked_material.is_library_indirect 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, # Only usage of this linked mesh is a runtime ID (object),
# so writing .blend file will have kept its tag to directly linked data. # so writing .blend file will have properly reset its tag to indirectly linked data.
assert not linked_mesh.is_library_indirect assert linked_mesh.is_library_indirect
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
assert 'Cube' not in bpy.data.objects assert 'Cube' not in bpy.data.objects
assert 'LibMaterial' in bpy.data.materials # Pulled-in by the linked mesh. assert 'LibMaterial' not in bpy.data.materials
linked_mesh = bpy.data.meshes['LibMesh'] assert 'libMesh' not in bpy.data.meshes
assert linked_mesh.use_fake_user is True
assert linked_mesh.is_library_indirect is False
TESTS = ( TESTS = (

View File

@ -214,9 +214,9 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper):
material = bpy.data.materials[0] material = bpy.data.materials[0]
assert material.library is not None assert material.library is not None
assert material.use_fake_user is True assert material.use_fake_user is False # Fake user is cleared when linking.
assert material.users == 2 # Fake user is not cleared when linking. assert material.users == 1
assert material.is_library_indirect assert material.is_library_indirect is True
assert mesh.library is not None assert mesh.library is not None
assert mesh.use_fake_user is False assert mesh.use_fake_user is False
@ -229,29 +229,28 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper):
coll.objects.link(ob) coll.objects.link(ob)
bpy.context.scene.collection.children.link(coll) bpy.context.scene.collection.children.link(coll)
assert material.users == 2 assert material.users == 1
assert material.is_library_indirect assert material.is_library_indirect is True
assert mesh.users == 1 assert mesh.users == 1
assert mesh.is_library_indirect is False assert mesh.is_library_indirect is False
ob.material_slots[0].link = 'OBJECT' ob.material_slots[0].link = 'OBJECT'
ob.material_slots[0].material = material ob.material_slots[0].material = material
assert material.users == 3 assert material.users == 2
assert material.is_library_indirect is False assert material.is_library_indirect is False
ob.material_slots[0].material = None 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. # This is not properly updated whene removing a local user of linked data.
assert material.is_library_indirect is False assert material.is_library_indirect is False
output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile")) 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) bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
assert material.users == 2 assert material.users == 1
# Currently linked data with 'fake user' set are considered as directly linked data. assert material.is_library_indirect is True
assert not material.is_library_indirect
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
@ -264,10 +263,9 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper):
material = bpy.data.materials[0] material = bpy.data.materials[0]
assert material.library is not None assert material.library is not None
assert material.use_fake_user is True assert material.use_fake_user is False # Fake user is cleared when linking.
assert material.users == 2 # Fake user is not cleared when linking. assert material.users == 1
# Currently linked data with 'fake user' set are considered as directly linked data. assert material.is_library_indirect is True
assert not material.is_library_indirect
assert mesh.library is not None assert mesh.library is not None
assert mesh.use_fake_user is False assert mesh.use_fake_user is False
@ -293,7 +291,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
assert len(bpy.data.materials) == 1 assert len(bpy.data.materials) == 1
assert bpy.data.materials[0].library is not None 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 len(bpy.data.meshes) == 1
assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].library is None
assert bpy.data.meshes[0].use_fake_user is False assert bpy.data.meshes[0].use_fake_user is False
@ -310,7 +308,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
assert len(bpy.data.materials) == 1 assert len(bpy.data.materials) == 1
assert bpy.data.materials[0].library is not None 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 len(bpy.data.meshes) == 1
assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].library is None
assert bpy.data.meshes[0].use_fake_user is False assert bpy.data.meshes[0].use_fake_user is False
@ -328,7 +326,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
assert len(bpy.data.materials) == 1 assert len(bpy.data.materials) == 1
assert bpy.data.materials[0].library is not None 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 len(bpy.data.meshes) == 1
assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].library is None
assert bpy.data.meshes[0].use_fake_user is True assert bpy.data.meshes[0].use_fake_user is True
@ -345,7 +343,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
assert len(bpy.data.materials) == 1 assert len(bpy.data.materials) == 1
assert bpy.data.materials[0].library is not None 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 len(bpy.data.meshes) == 1
assert bpy.data.meshes[0].library is None assert bpy.data.meshes[0].library is None
assert bpy.data.meshes[0].users == 1 assert bpy.data.meshes[0].users == 1