Address #105786: How to handle linked data with 'fake user' set.
133dde41bb changed how 'fake user' flag is handled with linked data.
Previous behavior was a bug/inconsistency, in that the 'directly linked'
tag would be 'over-set' and never cleared, forcing saving references to
a lot of unused linked data.
Note that ideally, 'Fake user' flag should be ignored, and the only way
to decide whether to keep or not a linked ID should be whether it's
actually used by some local data.
However, #103867 and #105687 show that this is causing issues in some cases,
where users wrongly relied on the linked data's pre-defined 'Fake user' flag
to keep their linked data in their production files, even if said data had no
real user.
While not ideal, for now we should consider 'fake user' flag for linked data
as a real usage case. A better handling of this edge-case is related to
wider designs aboud handling of 'non used' data on file save, whether
linked IDs should keep track of being explicitly or implicitly linked by
the user, etc.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user