diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index 299735ccab0..eb4975bebfe 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -1141,6 +1141,16 @@ static bool write_file_handle(Main *mainvar, * asap afterward. */ id_lib_extern(id_iter); } + else if (ID_FAKE_USERS(id_iter) > 0) { + /* 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 8495c8a7ad6..e635d9ea593 100644 --- a/tests/python/bl_blendfile_io.py +++ b/tests/python/bl_blendfile_io.py @@ -64,7 +64,7 @@ class TestIdRuntimeTag(TestHelper): bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True) obj = bpy.data.objects['Cube'] - assert obj.is_runtime_data == False + assert obj.is_runtime_data is False assert bpy.context.view_layer.depsgraph.ids['Cube'].is_runtime_data output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile")) @@ -72,7 +72,7 @@ class TestIdRuntimeTag(TestHelper): bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) obj = bpy.data.objects['Cube'] - assert obj.is_runtime_data == False + assert obj.is_runtime_data is False obj.is_runtime_data = True assert obj.is_runtime_data @@ -82,7 +82,7 @@ class TestIdRuntimeTag(TestHelper): assert 'Cube' not in bpy.data.objects mesh = bpy.data.meshes['Cube'] - assert mesh.is_runtime_data == False + assert mesh.is_runtime_data is False assert mesh.users == 0 def test_linking(self): @@ -91,7 +91,11 @@ class TestIdRuntimeTag(TestHelper): bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True) material = bpy.data.materials.new("LibMaterial") - material.use_fake_user = True + # Use a dummy mesh as user of the material, such that the material is saved + # without having to use fake user on it. + mesh = bpy.data.meshes.new("LibMesh") + mesh.materials.append(material) + mesh.use_fake_user = True output_lib_path = os.path.join(output_dir, self.unique_blendfile_name("blendlib_runtimetag_basic")) bpy.ops.wm.save_as_mainfile(filepath=output_lib_path, check_existing=False, compress=False) @@ -99,15 +103,22 @@ class TestIdRuntimeTag(TestHelper): bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True) obj = bpy.data.objects['Cube'] - assert obj.is_runtime_data == False + assert obj.is_runtime_data is False obj.is_runtime_data = True link_dir = os.path.join(output_lib_path, "Material") bpy.ops.wm.link(directory=link_dir, filename="LibMaterial") linked_material = bpy.data.materials['LibMaterial'] - assert linked_material.is_library_indirect == False + 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") + + linked_mesh = bpy.data.meshes['LibMesh'] + assert linked_mesh.is_library_indirect is False + + obj.data = linked_mesh obj.material_slots[0].link = 'OBJECT' obj.material_slots[0].material = linked_material @@ -118,13 +129,17 @@ 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 + bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) assert 'Cube' not in bpy.data.objects - assert 'LibMaterial' not in bpy.data.materials - mesh = bpy.data.meshes['Cube'] - assert mesh.is_runtime_data == False - assert mesh.users == 0 + 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 TESTS = ( diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index d467c749ddf..2166f34293d 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -248,8 +248,8 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper): bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False) assert material.users == 2 - # Currently linked data which has no more local user never gets reset to indirectly linked status. - assert material.is_library_indirect + # Currently linked data with 'fake user' set are considered as directly linked data. + assert not material.is_library_indirect bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False) @@ -264,9 +264,8 @@ class TestBlendLibLinkIndirect(TestBlendLibLinkHelper): 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 even re-reading the .blend file will not properly reset tag for indirectly linked data, - # if their reference was written in the .blend file. - assert material.is_library_indirect + # Currently linked data with 'fake user' set are considered as directly linked data. + assert not material.is_library_indirect assert mesh.library is not None assert mesh.use_fake_user is False