Overrides: API to create an override template.

This is functionality that isn't accessible via the user interface. The
API allows the creation and modification of an override template that
holds rules that needs to be checked when overriding the asset.

The API is setup that it cannot be changed after creation. Later on when
the system is more mature we will allow changing overrides operations.

NOTE: This is an experimental feature and should not be used in productions.

Reviewed By: mont29, sebbas

Differential Revision: https://developer.blender.org/D10792
This commit is contained in:
Jeroen Bakker 2021-03-29 09:53:12 +02:00
parent 27fa2507a1
commit 87f9405c9a
7 changed files with 326 additions and 59 deletions

View File

@ -2244,6 +2244,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
({"property": "use_switch_object_operator"}, "T80402"),
({"property": "use_sculpt_tools_tilt"}, "T82877"),
({"property": "use_asset_browser"}, ("project/profile/124/", "Milestone 1")),
({"property": "use_override_templates"}, ("T73318", "Milestone 4")),
),
)

View File

@ -74,6 +74,7 @@ bool BKE_lib_override_library_create(struct Main *bmain,
struct ViewLayer *view_layer,
struct ID *id_root,
struct ID *id_reference);
bool BKE_lib_override_library_template_create(struct ID *id);
bool BKE_lib_override_library_proxy_convert(struct Main *bmain,
struct Scene *scene,
struct ViewLayer *view_layer,

View File

@ -822,6 +822,22 @@ bool BKE_lib_override_library_create(
return success;
}
/**
* Create a library override template.
*/
bool BKE_lib_override_library_template_create(struct ID *id)
{
if (ID_IS_LINKED(id)) {
return false;
}
if (ID_IS_OVERRIDE_LIBRARY(id)) {
return false;
}
BKE_lib_override_library_init(id, NULL);
return true;
}
/**
* Convert a given proxy object into a library override.
*

View File

@ -646,7 +646,8 @@ typedef struct UserDef_Experimental {
char use_switch_object_operator;
char use_sculpt_tools_tilt;
char use_asset_browser;
char _pad[6];
char use_override_templates;
char _pad[5];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;

View File

@ -85,6 +85,47 @@ const EnumPropertyItem rna_enum_id_type_items[] = {
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem rna_enum_override_library_property_operation_items[] = {
{IDOVERRIDE_LIBRARY_OP_NOOP,
"NOOP",
0,
"No-Op",
"Does nothing, prevents adding actual overrides (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_REPLACE,
"REPLACE",
0,
"Replace",
"Replace value of reference by overriding one"},
{IDOVERRIDE_LIBRARY_OP_ADD,
"DIFF_ADD",
0,
"Differential",
"Stores and apply difference between reference and local value (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_SUBTRACT,
"DIFF_SUB",
0,
"Differential",
"Stores and apply difference between reference and local value (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_MULTIPLY,
"FACT_MULTIPLY",
0,
"Factor",
"Stores and apply multiplication factor between reference and local value (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_INSERT_AFTER,
"INSERT_AFTER",
0,
"Insert After",
"Insert a new item into collection after the one referenced in subitem_reference_name or "
"_index"},
{IDOVERRIDE_LIBRARY_OP_INSERT_BEFORE,
"INSERT_BEFORE",
0,
"Insert Before",
"Insert a new item into collection after the one referenced in subitem_reference_name or "
"_index (NOT USED)"},
{0, NULL, 0, NULL, NULL},
};
#ifdef RNA_RUNTIME
# include "DNA_anim_types.h"
@ -552,6 +593,65 @@ static ID *rna_ID_override_create(ID *id, Main *bmain, bool remap_local_usages)
return local_id;
}
static void rna_ID_override_template_create(ID *id, ReportList *reports)
{
if (!U.experimental.use_override_templates) {
BKE_report(reports, RPT_WARNING, "Override template experimental feature is disabled");
return;
}
if (ID_IS_LINKED(id)) {
BKE_report(reports, RPT_ERROR, "Unable to create override template for linked data-blocks");
return;
}
if (ID_IS_OVERRIDE_LIBRARY(id)) {
BKE_report(
reports, RPT_ERROR, "Unable to create override template for overridden data-blocks");
return;
}
BKE_lib_override_library_template_create(id);
}
static IDOverrideLibraryProperty *rna_ID_override_library_properties_add(
IDOverrideLibrary *override_library, ReportList *reports, const char rna_path[])
{
bool created;
IDOverrideLibraryProperty *result = BKE_lib_override_library_property_get(
override_library, rna_path, &created);
if (!created) {
BKE_report(reports, RPT_DEBUG, "No new override property created, property already exists");
}
return result;
}
static IDOverrideLibraryPropertyOperation *rna_ID_override_library_property_operations_add(
IDOverrideLibraryProperty *override_property,
ReportList *reports,
int operation,
const char *subitem_refname,
const char *subitem_locname,
int subitem_refindex,
int subitem_locindex)
{
bool created;
bool strict;
IDOverrideLibraryPropertyOperation *result = BKE_lib_override_library_property_operation_get(
override_property,
operation,
subitem_refname,
subitem_locname,
subitem_refindex,
subitem_locindex,
false,
&strict,
&created);
if (!created) {
BKE_report(reports, RPT_DEBUG, "No new override operation created, operation already exists");
}
return result;
}
static void rna_ID_update_tag(ID *id, Main *bmain, ReportList *reports, int flag)
{
/* XXX, new function for this! */
@ -1267,47 +1367,6 @@ static void rna_def_ID_override_library_property_operation(BlenderRNA *brna)
StructRNA *srna;
PropertyRNA *prop;
static const EnumPropertyItem override_library_property_operation_items[] = {
{IDOVERRIDE_LIBRARY_OP_NOOP,
"NOOP",
0,
"No-Op",
"Does nothing, prevents adding actual overrides (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_REPLACE,
"REPLACE",
0,
"Replace",
"Replace value of reference by overriding one"},
{IDOVERRIDE_LIBRARY_OP_ADD,
"DIFF_ADD",
0,
"Differential",
"Stores and apply difference between reference and local value (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_SUBTRACT,
"DIFF_SUB",
0,
"Differential",
"Stores and apply difference between reference and local value (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_MULTIPLY,
"FACT_MULTIPLY",
0,
"Factor",
"Stores and apply multiplication factor between reference and local value (NOT USED)"},
{IDOVERRIDE_LIBRARY_OP_INSERT_AFTER,
"INSERT_AFTER",
0,
"Insert After",
"Insert a new item into collection after the one referenced in subitem_reference_name or "
"_index"},
{IDOVERRIDE_LIBRARY_OP_INSERT_BEFORE,
"INSERT_BEFORE",
0,
"Insert Before",
"Insert a new item into collection after the one referenced in subitem_reference_name or "
"_index (NOT USED)"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem override_library_property_flag_items[] = {
{IDOVERRIDE_LIBRARY_FLAG_MANDATORY,
"MANDATORY",
@ -1329,7 +1388,7 @@ static void rna_def_ID_override_library_property_operation(BlenderRNA *brna)
prop = RNA_def_enum(srna,
"operation",
override_library_property_operation_items,
rna_enum_override_library_property_operation_items,
IDOVERRIDE_LIBRARY_OP_REPLACE,
"Operation",
"What override operation is performed");
@ -1386,6 +1445,66 @@ static void rna_def_ID_override_library_property_operation(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE); /* For now. */
}
static void rna_def_ID_override_library_property_operations(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_property_srna(cprop, "IDOverrideLibraryPropertyOperations");
srna = RNA_def_struct(brna, "IDOverrideLibraryPropertyOperations", NULL);
RNA_def_struct_sdna(srna, "IDOverrideLibraryProperty");
RNA_def_struct_ui_text(srna, "Override Operations", "Collection of override operations");
/* Add Property */
func = RNA_def_function(srna, "add", "rna_ID_override_library_property_operations_add");
RNA_def_function_ui_description(func, "Add a new operation");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_enum(func,
"operation",
rna_enum_override_library_property_operation_items,
IDOVERRIDE_LIBRARY_OP_REPLACE,
"Operation",
"What override operation is performed");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
parm = RNA_def_string(func,
"subitem_reference_name",
NULL,
INT_MAX,
"Subitem Reference Name",
"Used to handle insertions into collection");
parm = RNA_def_string(func,
"subitem_local_name",
NULL,
INT_MAX,
"Subitem Local Name",
"Used to handle insertions into collection");
parm = RNA_def_int(func,
"subitem_reference_index",
-1,
-1,
INT_MAX,
"Subitem Reference Index",
"Used to handle insertions into collection",
-1,
INT_MAX);
parm = RNA_def_int(func,
"subitem_local_index",
-1,
-1,
INT_MAX,
"Subitem Local Index",
"Used to handle insertions into collection",
-1,
INT_MAX);
parm = RNA_def_pointer(func,
"property",
"IDOverrideLibraryPropertyOperation",
"New Operation",
"Created operation");
RNA_def_function_return(func, parm);
}
static void rna_def_ID_override_library_property(BlenderRNA *brna)
{
StructRNA *srna;
@ -1405,18 +1524,47 @@ static void rna_def_ID_override_library_property(BlenderRNA *brna)
"RNA path leading to that property, from owning ID");
RNA_def_property_clear_flag(prop, PROP_EDITABLE); /* For now. */
RNA_def_collection(srna,
"operations",
"IDOverrideLibraryPropertyOperation",
"Operations",
"List of overriding operations for a property");
prop = RNA_def_collection(srna,
"operations",
"IDOverrideLibraryPropertyOperation",
"Operations",
"List of overriding operations for a property");
rna_def_ID_override_library_property_operations(brna, prop);
rna_def_ID_override_library_property_operation(brna);
}
static void rna_def_ID_override_library_properties(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_property_srna(cprop, "IDOverrideLibraryProperties");
srna = RNA_def_struct(brna, "IDOverrideLibraryProperties", NULL);
RNA_def_struct_sdna(srna, "IDOverrideLibrary");
RNA_def_struct_ui_text(srna, "Override Properties", "Collection of override properties");
/* Add Property */
func = RNA_def_function(srna, "add", "rna_ID_override_library_properties_add");
RNA_def_function_ui_description(
func, "Add a property to the override library when it doesn't exist yet");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_pointer(func,
"property",
"IDOverrideLibraryProperty",
"New Property",
"Newly created override property or existing one");
RNA_def_function_return(func, parm);
parm = RNA_def_string(
func, "rna_path", NULL, 256, "RNA Path", "RNA-Path of the property to add");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
}
static void rna_def_ID_override_library(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "IDOverrideLibrary", NULL);
RNA_def_struct_ui_text(
@ -1425,11 +1573,12 @@ static void rna_def_ID_override_library(BlenderRNA *brna)
RNA_def_pointer(
srna, "reference", "ID", "Reference ID", "Linked ID used as reference by this override");
RNA_def_collection(srna,
"properties",
"IDOverrideLibraryProperty",
"Properties",
"List of overridden properties");
prop = RNA_def_collection(srna,
"properties",
"IDOverrideLibraryProperty",
"Properties",
"List of overridden properties");
rna_def_ID_override_library_properties(brna, prop);
rna_def_ID_override_library_property(brna);
}
@ -1581,6 +1730,10 @@ static void rna_def_ID(BlenderRNA *brna)
"Whether local usages of the linked ID should be remapped to the new "
"library override of it");
func = RNA_def_function(srna, "override_template_create", "rna_ID_override_template_create");
RNA_def_function_ui_description(func, "Create an override template for this ID");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
func = RNA_def_function(srna, "user_clear", "rna_ID_user_clear");
RNA_def_function_ui_description(func,
"Clear the user count of a data-block so its not saved, "

View File

@ -6305,6 +6305,11 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
prop,
"Asset Browser",
"Enable Asset Browser editor and operators to manage data-blocks as asset");
prop = RNA_def_property(srna, "use_override_templates", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "use_override_templates", 1);
RNA_def_property_ui_text(
prop, "Override Templates", "Enable library override template in the python API");
}
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)

View File

@ -1,6 +1,6 @@
# Apache License, Version 2.0
# ./blender.bin --background -noaudio --python tests/python/bl_blendfile_library_overrides.py
# ./blender.bin --background -noaudio --python tests/python/bl_blendfile_library_overrides.py -- --output-dir=/tmp/
import pathlib
import bpy
import sys
@ -16,6 +16,8 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
OBJECT_LIBRARY_PARENT = "LibMeshParent"
MESH_LIBRARY_CHILD = "LibMeshChild"
OBJECT_LIBRARY_CHILD = "LibMeshChild"
MESH_LIBRARY_PERMISSIVE = "LibMeshPermissive"
OBJECT_LIBRARY_PERMISSIVE = "LibMeshPermissive"
def __init__(self, args):
self.args = args
@ -33,6 +35,14 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
obj_child = bpy.data.objects.new(TestLibraryOverrides.OBJECT_LIBRARY_CHILD, object_data=mesh_child)
obj_child.parent = obj
bpy.context.collection.objects.link(obj_child)
mesh = bpy.data.meshes.new(TestLibraryOverrides.MESH_LIBRARY_PERMISSIVE)
obj = bpy.data.objects.new(TestLibraryOverrides.OBJECT_LIBRARY_PERMISSIVE, object_data=mesh)
bpy.context.collection.objects.link(obj)
obj.override_template_create()
prop = obj.override_library.properties.add(rna_path='scale')
prop.operations.add(operation='NOOP')
bpy.ops.wm.save_as_mainfile(filepath=str(self.output_path), check_existing=False, compress=False)
def __ensure_override_library_updated(self):
@ -47,29 +57,108 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
bpy.ops.wm.link(directory=str(link_dir), filename=TestLibraryOverrides.OBJECT_LIBRARY_PARENT)
obj = bpy.data.objects[TestLibraryOverrides.OBJECT_LIBRARY_PARENT]
assert(obj.override_library is None)
self.assertIsNone(obj.override_library)
local_id = obj.override_create()
assert(local_id.override_library)
assert(local_id.data.override_library is None)
self.assertIsNotNone(local_id.override_library)
self.assertIsNone(local_id.data.override_library)
assert(len(local_id.override_library.properties) == 0)
local_id.location.y = 1.0
self.__ensure_override_library_updated()
assert (len(local_id.override_library.properties) == 1)
assert(len(local_id.override_library.properties) == 1)
override_prop = local_id.override_library.properties[0]
assert(override_prop.rna_path == "location");
assert(len(override_prop.operations) == 1)
override_operation = override_prop.operations[0]
assert (override_operation.operation == 'REPLACE')
assert(override_operation.operation == 'REPLACE')
# Setting location.y overridded all elements in the location array. -1 is a wildcard.
assert(override_operation.subitem_local_index == -1)
def test_link_permissive(self):
"""
Linked assets with a permissive template.
- Checks if the NOOP is properly handled.
- Checks if the correct properties and operations are created/updated.
"""
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
bpy.data.orphans_purge()
link_dir = self.output_path / "Object"
bpy.ops.wm.link(directory=str(link_dir), filename=TestLibraryOverrides.OBJECT_LIBRARY_PERMISSIVE)
obj = bpy.data.objects[TestLibraryOverrides.OBJECT_LIBRARY_PERMISSIVE]
self.assertIsNotNone(obj.override_library)
local_id = obj.override_create()
self.assertIsNotNone(local_id.override_library)
self.assertIsNone(local_id.data.override_library)
assert(len(local_id.override_library.properties) == 1)
override_prop = local_id.override_library.properties[0]
assert(override_prop.rna_path == "scale");
assert(len(override_prop.operations) == 1)
override_operation = override_prop.operations[0]
assert(override_operation.operation == 'NOOP')
assert(override_operation.subitem_local_index == -1)
local_id.location.y = 1.0
local_id.scale.x = 0.5
# XXX(jbakker): This change should not applied. But it is as the library
# overrides aren't updated yet. We should expect 1.0
assert(local_id.scale.x == 0.5)
assert(local_id.location.y == 1.0)
self.__ensure_override_library_updated()
assert(local_id.scale.x == 1.0)
assert(local_id.location.y == 1.0)
assert(len(local_id.override_library.properties) == 2)
override_prop = local_id.override_library.properties[0]
assert(override_prop.rna_path == "scale");
assert(len(override_prop.operations) == 1)
override_operation = override_prop.operations[0]
assert(override_operation.operation == 'NOOP')
assert(override_operation.subitem_local_index == -1)
override_prop = local_id.override_library.properties[1]
assert(override_prop.rna_path == "location");
assert(len(override_prop.operations) == 1)
override_operation = override_prop.operations[0]
assert(override_operation.operation == 'REPLACE')
assert (override_operation.subitem_local_index == -1)
class TestLibraryTemplate(TestHelper, unittest.TestCase):
MESH_LIBRARY_PERMISSIVE = "LibMeshPermissive"
OBJECT_LIBRARY_PERMISSIVE = "LibMeshPermissive"
def __init__(self, args):
pass
def test_permissive_template(self):
"""
Test setting up a permissive template.
"""
bpy.ops.wm.read_homefile(use_empty=True, use_factory_startup=True)
mesh = bpy.data.meshes.new(TestLibraryTemplate.MESH_LIBRARY_PERMISSIVE)
obj = bpy.data.objects.new(TestLibraryTemplate.OBJECT_LIBRARY_PERMISSIVE, object_data=mesh)
bpy.context.collection.objects.link(obj)
assert(obj.override_library is None)
obj.override_template_create()
assert(obj.override_library is not None)
assert(len(obj.override_library.properties) == 0)
prop = obj.override_library.properties.add(rna_path='scale')
assert(len(obj.override_library.properties) == 1)
assert(len(prop.operations) == 0)
operation = prop.operations.add(operation='NOOP')
assert(len(prop.operations) == 1)
assert(operation.operation == 'NOOP')
TESTS = (
TestLibraryOverrides,
TestLibraryTemplate,
)
@ -95,6 +184,7 @@ def main():
# Don't write thumbnails into the home directory.
bpy.context.preferences.filepaths.use_save_preview_images = False
bpy.context.preferences.experimental.use_override_templates = True
for Test in TESTS:
Test(args).run_all_tests()