Add concept of 'runtime' ID in Main data-base.

Such IDs are tagged with the new `LIB_TAG_RUNTIME`. They are essentially
like any other regular ID, except that they do not get written in .blend
files. They also do not make their linked data usages directly linked.
They do be written in undo steps however.

This tag should be ignored in any non-Main IDs (e.g. evaluated data,
`NO_MAIN`, etc.).

This commit also adds a new RNA ID property, `is_runtime`. This is not a
direct mapping to the DNA tag, as a non-main ID will also return True,
and the property is only editable for Main IDs.

Some basic testing for expected behavior of that new tag was also added
to `blendfile_io` py unittest.

Required for brush asset project, see T101908.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D16675
This commit is contained in:
Bastien Montagne 2022-12-16 10:13:40 +09:00 committed by Bastien Montagne
parent 9837a32822
commit b93025db59
Notes: blender-bot 2023-02-14 08:38:14 +01:00
Referenced by issue #103389, Invalid flags in default_material_surface->nodetree->tag.
Referenced by issue #101908, Brush Asset - Technical Core-level Design
5 changed files with 149 additions and 3 deletions

View File

@ -2020,8 +2020,15 @@ static void direct_link_id_common(
/* When actually reading a file, we do want to reset/re-generate session UUIDS.
* In undo case, we want to re-use existing ones. */
id->session_uuid = MAIN_ID_SESSION_UUID_UNSET;
/* Runtime IDs should never be written in .blend files (except memfiles from undo). */
BLI_assert((id->tag & LIB_TAG_RUNTIME) == 0);
}
/* No-main and other types of special IDs should never be written in .blend files. */
BLI_assert((id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT | LIB_TAG_NOT_ALLOCATED)) ==
0);
if ((tag & LIB_TAG_TEMP_MAIN) == 0) {
BKE_lib_libblock_session_uuid_ensure(id);
}

View File

@ -1097,7 +1097,10 @@ static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_
}
BLI_assert(!ID_IS_LINKED(id_self));
BLI_assert((cb_flag & IDWALK_CB_INDIRECT_USAGE) == 0);
UNUSED_VARS_NDEBUG(id_self);
if (id_self->tag & LIB_TAG_RUNTIME) {
return IDWALK_RET_NOP;
}
if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK) {
id_lib_indirect_weak_link(id);
@ -1207,12 +1210,18 @@ static bool write_file_handle(Main *mainvar,
/* We only write unused IDs in undo case.
* NOTE: All Scenes, WindowManagers and WorkSpaces should always be written to disk, so
* their user-count should never be nullptr currently. */
* their user-count should never be zero currently. */
if (id->us == 0 && !wd->use_memfile) {
BLI_assert(!ELEM(GS(id->name), ID_SCE, ID_WM, ID_WS));
continue;
}
if ((id->tag & LIB_TAG_RUNTIME) != 0 && !wd->use_memfile) {
/* Runtime IDs are never written to .blend files, and they should not influence
* (in)direct status of linked IDs they may use. */
continue;
}
const bool do_override = !ELEM(override_storage, nullptr, bmain) &&
ID_IS_OVERRIDE_LIBRARY_REAL(id);

View File

@ -808,6 +808,16 @@ enum {
* RESET_NEVER
*/
LIB_TAG_NO_MAIN = 1 << 15,
/**
* ID is considered as runtime, and should not be saved when writing .blend file, nor influence
* (in)direct status of linked data.
*
* Only meaningful for IDs belonging to regular Main database, all other cases are implicitely
* considered runtime-only.
*
* RESET_NEVER
*/
LIB_TAG_RUNTIME = 1 << 22,
/**
* Datablock does not refcount usages of other IDs.
*
@ -855,7 +865,7 @@ enum {
*
* However a few of these need to be explicitely preserved accross undo steps.
*/
#define LIB_TAG_KEEP_ON_UNDO (LIB_TAG_EXTRAUSER | LIB_TAG_MISSING)
#define LIB_TAG_KEEP_ON_UNDO (LIB_TAG_EXTRAUSER | LIB_TAG_MISSING | LIB_TAG_RUNTIME)
/* Tag given ID for an update in all the dependency graphs. */
typedef enum IDRecalcFlag {

View File

@ -574,6 +574,33 @@ IDProperty **rna_ID_idprops(PointerRNA *ptr)
return &id->properties;
}
int rna_ID_is_runtime_editable(PointerRNA *ptr, const char **r_info)
{
ID *id = (ID *)ptr->data;
/* TODO: This should be abstracted in a BKE function or define, somewhat related to T88555. */
if (id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_TEMP_MAIN | LIB_TAG_LOCALIZED |
LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT | LIB_TAG_COPIED_ON_WRITE)) {
*r_info =
"Cannot edit 'runtime' status of non-blendfile data-blocks, as they are by definition "
"always runtime";
return 0;
}
return PROP_EDITABLE;
}
bool rna_ID_is_runtime_get(PointerRNA *ptr)
{
ID *id = (ID *)ptr->data;
/* TODO: This should be abstracted in a BKE function or define, somewhat related to T88555. */
if (id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_TEMP_MAIN | LIB_TAG_LOCALIZED |
LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT | LIB_TAG_COPIED_ON_WRITE)) {
return true;
}
return (id->tag & LIB_TAG_RUNTIME) != 0;
}
void rna_ID_fake_user_set(PointerRNA *ptr, bool value)
{
ID *id = (ID *)ptr->data;
@ -2030,6 +2057,17 @@ static void rna_def_ID(BlenderRNA *brna)
"This data-block is not an independent one, but is actually a sub-data of another ID "
"(typical example: root node trees or master collections)");
prop = RNA_def_property(srna, "is_runtime_data", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "tag", LIB_TAG_RUNTIME);
RNA_def_property_editable_func(prop, "rna_ID_is_runtime_editable");
RNA_def_property_boolean_funcs(prop, "rna_ID_is_runtime_get", NULL);
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
RNA_def_property_ui_text(prop,
"Runtime Data",
"This data-block is runtime data, i.e. it won't be saved in .blend "
"file. Note that e.g. evaluated IDs are always runtime, so this value "
"is only editable for data-blocks in Main data-base");
prop = RNA_def_property(srna, "tag", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "tag", LIB_TAG_DOIT);
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);

View File

@ -47,8 +47,90 @@ class TestBlendFileSaveLoadBasic(TestHelper):
assert orig_data == read_data
# NOTE: Technically this should rather be in `bl_id_management.py` test, but that file uses `unittest` module,
# which makes mixing it with tests system used here and passing extra parameters complicated.
# Since the main effect of 'RUNTIME' ID tag is on file save, it can as well be here for now.
class TestIdRuntimeTag(TestHelper):
def __init__(self, args):
self.args = args
def unique_blendfile_name(self, base_name):
return base_name + self.__class__.__name__ + ".blend"
def test_basics(self):
output_dir = self.args.output_dir
self.ensure_path(output_dir)
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
obj = bpy.data.objects['Cube']
assert obj.is_runtime_data == False
assert bpy.context.view_layer.depsgraph.ids['Cube'].is_runtime_data == True
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.open_mainfile(filepath=output_work_path, load_ui=False)
obj = bpy.data.objects['Cube']
assert obj.is_runtime_data == False
obj.is_runtime_data = True
assert obj.is_runtime_data == True
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
assert 'Cube' not in bpy.data.objects
mesh = bpy.data.meshes['Cube']
assert mesh.is_runtime_data == False
assert mesh.users == 0
def test_linking(self):
output_dir = self.args.output_dir
self.ensure_path(output_dir)
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
material = bpy.data.materials.new("LibMaterial")
material.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)
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
obj = bpy.data.objects['Cube']
assert obj.is_runtime_data == 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
obj.material_slots[0].link = 'OBJECT'
obj.material_slots[0].material = linked_material
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)
# Only usage of this linked material is a runtime ID (object),
# so writing .blend file will have properly reset its tag to indirectly linked data.
assert linked_material.is_library_indirect == True
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
TESTS = (
TestBlendFileSaveLoadBasic,
TestIdRuntimeTag,
)