diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 96695ff1be5..f44cf23fb58 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -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")), ), ) diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index f69580d38be..0750a3332a8 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -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, diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index c755d153100..dbb46aa5780 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -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. * diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 4595b12e9d4..334669d3433 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 6e2005b7314..8d9e7852b4c 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -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, " diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 6f1884f50f4..baf77aae87f 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -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) diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py index ab75a410590..ccb2fcc01c8 100644 --- a/tests/python/bl_blendfile_library_overrides.py +++ b/tests/python/bl_blendfile_library_overrides.py @@ -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()