Blender V4.3
tree_element_overrides.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BKE_collection.hh"
10#include "BKE_lib_override.hh"
11
12#include "BLI_function_ref.hh"
14#include "BLI_map.hh"
15#include "BLI_utildefines.h"
16
17#include "BLT_translation.hh"
18
19#include "DNA_space_types.h"
20
21#include "RNA_access.hh"
22#include "RNA_path.hh"
23
24#include "../outliner_intern.hh"
25
26#include "tree_display.hh"
27#include "tree_element_label.hh"
29
30#include <stdexcept>
31
32namespace blender::ed::outliner {
33
35 SpaceOutliner &space_outliner_;
37
38 public:
40 void build_path(TreeElement &parent, TreeElementOverridesData &override_data, short &index);
41
42 private:
43 TreeElement &ensure_label_element_for_prop(
44 TreeElement &parent, StringRef elem_path, PointerRNA &ptr, PropertyRNA &prop, short &index);
45 TreeElement &ensure_label_element_for_ptr(TreeElement &parent,
46 StringRef elem_path,
48 short &index);
49 void ensure_entire_collection(TreeElement &te_to_expand,
50 const TreeElementOverridesData &override_data,
51 const char *coll_prop_path,
52 short &index);
53};
54
55/* -------------------------------------------------------------------- */
64 : AbstractTreeElement(legacy_te), id(id)
65{
67 if (legacy_te.parent != nullptr &&
69 {
70 legacy_te.name = IFACE_("Library Overrides");
71 }
72 else {
73 legacy_te.name = id.name + 2;
74 }
75}
76
78{
80 return RPT_("This override data-block is not needed anymore, but was detected as user-edited");
81 }
82
83 if (ID_IS_OVERRIDE_LIBRARY_REAL(&id) && ID_REAL_USERS(&id) == 0) {
84 return RPT_("This override data-block is unused");
85 }
86
87 return {};
88}
89
91 const bool show_system_overrides,
93{
94 PointerRNA override_rna_ptr;
95 PropertyRNA *override_rna_prop;
96
98
99 for (IDOverrideLibraryProperty *override_prop :
100 ListBaseWrapper<IDOverrideLibraryProperty>(id.override_library->properties))
101 {
102 int rnaprop_index = 0;
103 const bool is_rna_path_valid = BKE_lib_override_rna_property_find(
104 &idpoin, override_prop, &override_rna_ptr, &override_rna_prop, &rnaprop_index);
105
106 /* Check for conditions where the liboverride property should be considered as a system
107 * override, if needed. */
108 if (is_rna_path_valid && !show_system_overrides) {
109 bool do_skip = true;
110 bool is_system_override = false;
111
112 /* Matching ID pointers are considered as system overrides. */
113 if (ELEM(override_prop->rna_prop_type, PROP_POINTER, PROP_COLLECTION) &&
114 RNA_struct_is_ID(RNA_property_pointer_type(&override_rna_ptr, override_rna_prop)))
115 {
116 for (IDOverrideLibraryPropertyOperation *override_prop_op :
118 {
119 if ((override_prop_op->flag & LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) {
120 do_skip = false;
121 break;
122 }
123 is_system_override = true;
124 }
125 }
126
127 /* Animated/driven properties are considered as system overrides. */
128 if (!is_system_override && !BKE_lib_override_library_property_is_animated(
129 &id, override_prop, override_rna_prop, rnaprop_index))
130 {
131 do_skip = false;
132 }
133
134 if (do_skip) {
135 continue;
136 }
137 }
138
140 id, *override_prop, override_rna_ptr, *override_rna_prop, is_rna_path_valid};
141
142 fn(data);
143 }
144}
145
147{
148 BLI_assert(id.override_library != nullptr);
149
150 const bool show_system_overrides = (SUPPORT_FILTER_OUTLINER(&space_outliner) &&
151 (space_outliner.filter & SO_FILTER_SHOW_SYSTEM_OVERRIDES) !=
152 0);
153
154 OverrideRNAPathTreeBuilder path_builder(space_outliner);
155 short index = 0;
156
157 iterate_properties_to_display(id, show_system_overrides, [&](TreeElementOverridesData &data) {
158 path_builder.build_path(legacy_te_, data, index);
159 });
160}
161
164/* -------------------------------------------------------------------- */
172 TreeElementOverridesData &override_data)
173 : AbstractTreeElement(legacy_te),
174 override_rna_ptr(override_data.override_rna_ptr),
175 override_rna_prop(override_data.override_rna_prop),
176 rna_path(override_data.override_property.rna_path),
177 is_rna_path_valid(override_data.is_rna_path_valid)
178{
181
182 legacy_te.name = RNA_property_ui_name(&override_data.override_rna_prop);
183}
184
186{
187 if (!is_rna_path_valid) {
188 return RPT_(
189 "This override property does not exist in current data, it will be removed on "
190 "next .blend file save");
191 }
192
193 return {};
194}
195
198/* -------------------------------------------------------------------- */
205 TreeElement &legacy_te, TreeElementOverridesData &override_data)
206 : TreeElementOverridesProperty(legacy_te, override_data)
207{
210 "Override operations are only supported for collections right now");
211 /* Quiet Clang Static Analyzer warning by throwing instead of asserting (possible
212 * null-dereference). */
213 if (!override_data.operation) {
214 throw std::invalid_argument("missing operation");
215 }
216
217 operation_ = std::make_unique<IDOverrideLibraryPropertyOperation>(*override_data.operation);
218 /* Just for extra sanity. */
219 operation_->next = operation_->prev = nullptr;
220
221 if (std::optional<PointerRNA> col_item_ptr = get_collection_ptr()) {
222 const char *dyn_name = RNA_struct_name_get_alloc(&*col_item_ptr, nullptr, 0, nullptr);
223 if (dyn_name) {
224 legacy_te.name = dyn_name;
225 legacy_te.flag |= TE_FREE_NAME;
226 }
227 else {
228 legacy_te.name = RNA_struct_ui_name(col_item_ptr->type);
229 }
230 }
231}
232
234{
235 switch (operation_->operation) {
238 return RPT_("Added through override");
240 /* Returning nothing so that drawing code shows actual RNA button instead. */
241 return {};
242 /* Following cases are not expected in regular situation, but could be found in experimental
243 * files. */
245 return RPT_("Protected from override");
247 return RPT_("Additive override");
249 return RPT_("Subtractive override");
251 return RPT_("Multiplicative override");
252 default:
254 return {};
255 }
256}
257
259{
260 if (const std::optional<PointerRNA> col_item_ptr = get_collection_ptr()) {
261 return RNA_struct_ui_icon(col_item_ptr->type);
262 }
263
264 return {};
265}
266
267std::optional<PointerRNA> TreeElementOverridesPropertyOperation::get_collection_ptr() const
268{
269 PointerRNA col_item_ptr;
272 operation_->subitem_local_index,
273 &col_item_ptr))
274 {
275 return col_item_ptr;
276 }
277
278 return {};
279}
280
283/* -------------------------------------------------------------------- */
307 : space_outliner_(space_outliner)
308{
309}
310
312 TreeElementOverridesData &override_data,
313 short &index)
314{
315 PointerRNA idpoin = RNA_id_pointer_create(&override_data.id);
316
317 ListBase path_elems = {nullptr};
318 if (!RNA_path_resolve_elements(&idpoin, override_data.override_property.rna_path, &path_elems)) {
319 return;
320 }
321
322 const char *elem_path = nullptr;
323 TreeElement *te_to_expand = &parent;
324 char name_buf[128], *name;
325
326 LISTBASE_FOREACH (PropertyElemRNA *, elem, &path_elems) {
327 if (!elem->next) {
328 /* The last element is added as #TSE_LIBRARY_OVERRIDE below. */
329 break;
330 }
331 const char *previous_path = elem_path;
332 const char *new_path = RNA_path_append(previous_path, &elem->ptr, elem->prop, -1, nullptr);
333
334 te_to_expand = &ensure_label_element_for_prop(
335 *te_to_expand, new_path, elem->ptr, *elem->prop, index);
336
337 /* Above the collection property was added (e.g. "Modifiers"), to get the actual collection
338 * item the path refers to, we have to peek at the following path element and add a tree
339 * element for its pointer (e.g. "My Subdiv Modifier"). */
340 if (RNA_property_type(elem->prop) == PROP_COLLECTION) {
341 const int coll_item_idx = RNA_property_collection_lookup_index(
342 &elem->ptr, elem->prop, &elem->next->ptr);
343 name = RNA_struct_name_get_alloc(&elem->next->ptr, name_buf, sizeof(name_buf), nullptr);
344 const char *coll_item_path = RNA_path_append(
345 previous_path, &elem->ptr, elem->prop, coll_item_idx, name);
346 if (name && (name != name_buf)) {
347 MEM_freeN(name);
348 }
349
350 te_to_expand = &ensure_label_element_for_ptr(
351 *te_to_expand, coll_item_path, elem->next->ptr, index);
352
353 MEM_delete(new_path);
354 new_path = coll_item_path;
355 }
356
357 if (new_path) {
358 MEM_delete(elem_path);
359 elem_path = new_path;
360 }
361 }
362 LISTBASE_FOREACH_MUTABLE (PropertyElemRNA *, elem, &path_elems) {
363 MEM_delete(elem);
364 }
365 BLI_listbase_clear(&path_elems);
366
367 /* Special case: Overriding collections, e.g. adding or removing items. In this case we add
368 * elements for all collection items to show full context, and indicate which ones were
369 * added/removed (currently added only). Note that a single collection override may add/remove
370 * multiple items. */
371 if (RNA_property_type(&override_data.override_rna_prop) == PROP_COLLECTION) {
372 /* Tree element for the actual collection item (e.g. "Modifiers"). Can just use the override
373 * ptr & prop here, since they point to the collection property (e.g. `modifiers`). */
374 te_to_expand = &ensure_label_element_for_prop(*te_to_expand,
375 override_data.override_property.rna_path,
376 override_data.override_rna_ptr,
377 override_data.override_rna_prop,
378 index);
379
380 ensure_entire_collection(*te_to_expand, override_data, elem_path, index);
381 }
382 /* Some properties have multiple operations (e.g. an array property with multiple changed
383 * values), so the element may already be present. At this point they are displayed as a single
384 * property in the tree, so don't add it multiple times here. */
385 else if (!path_te_map.contains(override_data.override_property.rna_path)) {
386 AbstractTreeDisplay::add_element(&space_outliner_,
387 &te_to_expand->subtree,
388 &override_data.id,
389 &override_data,
390 te_to_expand,
392 index++);
393 }
394
395 MEM_delete(elem_path);
396}
397
398void OverrideRNAPathTreeBuilder::ensure_entire_collection(
399 TreeElement &te_to_expand,
400 const TreeElementOverridesData &override_data,
401 /* The path of the owning collection property. */
402 const char *coll_prop_path,
403 short &index)
404{
405 BLI_assert(tree_element_cast<AbstractTreeElement>(&te_to_expand) != nullptr);
406
407 TreeElement *previous_te = nullptr;
408 int item_idx = 0;
409 char name_buf[128], *name;
410 RNA_PROP_BEGIN (&override_data.override_rna_ptr, itemptr, &override_data.override_rna_prop) {
411 name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr);
412 const char *coll_item_path = RNA_path_append(coll_prop_path,
413 &override_data.override_rna_ptr,
414 &override_data.override_rna_prop,
415 item_idx,
416 name);
417 if (name && (name != name_buf)) {
418 MEM_freeN(name);
419 }
420 IDOverrideLibraryPropertyOperation *item_operation =
422 nullptr,
423 nullptr,
424 {},
425 {},
426 -1,
427 item_idx,
428 false,
429 nullptr);
430 TreeElement *current_te = nullptr;
431
432 TreeElement *existing_te = path_te_map.lookup_default(coll_item_path, nullptr);
433
434 if (existing_te) {
435 /* Reinsert the element to make sure the order is right. It may have been inserted by a
436 * previous override. */
437 BLI_remlink(&te_to_expand.subtree, existing_te);
438 BLI_insertlinkafter(&te_to_expand.subtree, previous_te, existing_te);
439 current_te = existing_te;
440 }
441 /* Is there an operation for this item (added or removed the item to/from the collection)? If
442 * so indicate it as override using #TSE_LIBRARY_OVERRIDE_OPERATION. Otherwise it's just a
443 * regular collection we display for context. */
444 else if (item_operation) {
445 TreeElementOverridesData override_op_data = override_data;
446 override_op_data.operation = item_operation;
447
448 current_te = AbstractTreeDisplay::add_element(&space_outliner_,
449 &te_to_expand.subtree,
450 &override_op_data.id,
451 /* Element will store a copy. */
452 &override_op_data,
453 &te_to_expand,
455 index++);
456 }
457 else {
458 current_te = &ensure_label_element_for_ptr(te_to_expand, coll_item_path, itemptr, index);
459 }
460
461 MEM_delete(coll_item_path);
462 item_idx++;
463 previous_te = current_te;
464 }
466}
467
469{
470 BIFIconID icon = RNA_property_ui_icon(&prop);
471 if (icon) {
472 return icon;
473 }
474
475 /* Try if the collection item type has a dedicated icon (e.g. #ICON_MODIFIER for the
476 * #Object.modifiers property). */
477 if (RNA_property_type(&prop) == PROP_COLLECTION) {
478 const StructRNA *coll_ptr_type = RNA_property_pointer_type(&ptr, &prop);
479 icon = RNA_struct_ui_icon(coll_ptr_type);
480 if (icon != ICON_DOT) {
481 return icon;
482 }
483 }
484
485 return ICON_NONE;
486}
487
488TreeElement &OverrideRNAPathTreeBuilder::ensure_label_element_for_prop(
489 TreeElement &parent, StringRef elem_path, PointerRNA &ptr, PropertyRNA &prop, short &index)
490{
491 return *path_te_map.lookup_or_add_cb(elem_path, [&]() {
492 TreeElement *new_te = AbstractTreeDisplay::add_element(&space_outliner_,
493 &parent.subtree,
494 nullptr,
495 (void *)RNA_property_ui_name(&prop),
496 &parent,
498 index++,
499 false);
500 TreeElementLabel *te_label = tree_element_cast<TreeElementLabel>(new_te);
501
502 te_label->set_icon(get_property_icon(ptr, prop));
503 return new_te;
504 });
505}
506
507TreeElement &OverrideRNAPathTreeBuilder::ensure_label_element_for_ptr(TreeElement &parent,
508 StringRef elem_path,
510 short &index)
511{
512 return *path_te_map.lookup_or_add_cb(elem_path, [&]() {
513 const char *dyn_name = RNA_struct_name_get_alloc(&ptr, nullptr, 0, nullptr);
514
515 TreeElement *new_te = AbstractTreeDisplay::add_element(
516 &space_outliner_,
517 &parent.subtree,
518 nullptr,
519 (void *)(dyn_name ? dyn_name : RNA_struct_ui_name(ptr.type)),
520 &parent,
522 index++);
523 TreeElementLabel *te_label = tree_element_cast<TreeElementLabel>(new_te);
524 te_label->set_icon(RNA_struct_ui_icon(ptr.type));
525
526 MEM_delete(dyn_name);
527
528 return new_te;
529 });
530}
531
534} // namespace blender::ed::outliner
bool BKE_lib_override_library_property_is_animated(const ID *id, const IDOverrideLibraryProperty *liboverride_prop, const PropertyRNA *override_rna_prop, const int rnaprop_index)
IDOverrideLibraryPropertyOperation * BKE_lib_override_library_property_operation_find(IDOverrideLibraryProperty *liboverride_property, const char *subitem_refname, const char *subitem_locname, const std::optional< const ID * > &subitem_refid, const std::optional< const ID * > &subitem_locid, int subitem_refindex, int subitem_locindex, bool strict, bool *r_strict)
bool BKE_lib_override_rna_property_find(PointerRNA *idpoin, const IDOverrideLibraryProperty *library_prop, PointerRNA *r_override_poin, PropertyRNA **r_override_prop, int *r_index)
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define LISTBASE_FOREACH(type, var, list)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
void BLI_insertlinkafter(struct ListBase *listbase, void *vprevlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:331
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:130
#define ELEM(...)
#define RPT_(msgid)
#define IFACE_(msgid)
#define ID_IS_OVERRIDE_LIBRARY_REAL(_id)
Definition DNA_ID.h:676
@ LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE
Definition DNA_ID.h:255
@ ID_FLAG_LIB_OVERRIDE_RESYNC_LEFTOVER
Definition DNA_ID.h:741
@ LIBOVERRIDE_OP_NOOP
Definition DNA_ID.h:227
@ LIBOVERRIDE_OP_ADD
Definition DNA_ID.h:232
@ LIBOVERRIDE_OP_SUBTRACT
Definition DNA_ID.h:234
@ LIBOVERRIDE_OP_REPLACE
Definition DNA_ID.h:229
@ LIBOVERRIDE_OP_MULTIPLY
Definition DNA_ID.h:236
@ LIBOVERRIDE_OP_INSERT_BEFORE
Definition DNA_ID.h:240
@ LIBOVERRIDE_OP_INSERT_AFTER
Definition DNA_ID.h:239
#define ID_REAL_USERS(id)
Definition DNA_ID.h:637
@ TSE_LIBRARY_OVERRIDE_OPERATION
@ TSE_LIBRARY_OVERRIDE
@ TSE_LIBRARY_OVERRIDE_BASE
@ TSE_LAYER_COLLECTION
@ TSE_GENERIC_LABEL
@ TSE_SOME_ID
@ SO_FILTER_SHOW_SYSTEM_OVERRIDES
#define RNA_PROP_END
#define RNA_PROP_BEGIN(sptr, itemptr, prop)
@ PROP_POINTER
Definition RNA_types.hh:70
@ PROP_COLLECTION
Definition RNA_types.hh:71
int BIFIconID
static TreeElement * add_element(SpaceOutliner *space_outliner, ListBase *lb, ID *owner_id, void *create_data, TreeElement *parent, short type, short index, const bool expand=true)
void build_path(TreeElement &parent, TreeElementOverridesData &override_data, short &index)
TreeElementOverridesPropertyOperation(TreeElement &legacy_te, TreeElementOverridesData &override_data)
TreeElementOverridesProperty(TreeElement &legacy_te, TreeElementOverridesData &override_data)
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static void iterate_properties_to_display(ID &id, const bool show_system_overrides, FunctionRef< void(TreeElementOverridesData &data)> fn)
TreeElementT * tree_element_cast(const TreeElement *te)
static BIFIconID get_property_icon(PointerRNA &ptr, PropertyRNA &prop)
ListBaseWrapperTemplate< ListBase, T > ListBaseWrapper
#define SUPPORT_FILTER_OUTLINER(space_outliner_)
bool RNA_struct_is_ID(const StructRNA *type)
int RNA_property_ui_icon(const PropertyRNA *prop)
int RNA_property_collection_lookup_index(PointerRNA *ptr, PropertyRNA *prop, const PointerRNA *t_ptr)
StructRNA * RNA_property_pointer_type(PointerRNA *ptr, PropertyRNA *prop)
char * RNA_struct_name_get_alloc(PointerRNA *ptr, char *fixedbuf, int fixedlen, int *r_len)
PropertyType RNA_property_type(PropertyRNA *prop)
int RNA_struct_ui_icon(const StructRNA *type)
bool RNA_property_collection_lookup_int(PointerRNA *ptr, PropertyRNA *prop, int key, PointerRNA *r_ptr)
const char * RNA_struct_ui_name(const StructRNA *type)
const char * RNA_property_ui_name(const PropertyRNA *prop)
PointerRNA RNA_id_pointer_create(ID *id)
bool RNA_path_resolve_elements(PointerRNA *ptr, const char *path, ListBase *r_elements)
Definition rna_path.cc:601
char * RNA_path_append(const char *path, const PointerRNA *, PropertyRNA *prop, int intkey, const char *strkey)
Definition rna_path.cc:606
Definition DNA_ID.h:413
StructRNA * type
Definition RNA_types.hh:41
Establish and manage Outliner trees for different display modes.
PointerRNA * ptr
Definition wm_files.cc:4126
uint8_t flag
Definition wm_window.cc:138