Blender V5.0
outliner_tree.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2004 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cstring>
11
12#include "MEM_guardedalloc.h"
13
15
16#include "BLI_fnmatch.h"
17#include "BLI_listbase.h"
18#include "BLI_mempool.h"
19#include "BLI_rect.h"
20#include "BLI_string.h"
21#include "BLI_utildefines.h"
22
23#include "BKE_layer.hh"
24#include "BKE_main.hh"
25#include "BKE_modifier.hh"
27#include "BKE_screen.hh"
28
29#include "ED_outliner.hh"
30#include "ED_screen.hh"
31
32#include "UI_interface.hh"
33
34#include "outliner_intern.hh"
35#include "tree/tree_display.hh"
36#include "tree/tree_element.hh"
37
38#ifdef WIN32
39# include "BLI_math_base.h" /* M_PI */
40#endif
41
42namespace blender::ed::outliner {
43
44/* prototypes */
45static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner);
46
47/* -------------------------------------------------------------------- */
50
51static void outliner_storage_cleanup(SpaceOutliner *space_outliner)
52{
53 BLI_mempool *ts = space_outliner->treestore;
54
55 if (ts) {
56 TreeStoreElem *tselem;
57 int unused = 0;
58
59 /* each element used once, for ID blocks with more users to have each a treestore */
61
62 BLI_mempool_iternew(ts, &iter);
63 while ((tselem = static_cast<TreeStoreElem *>(BLI_mempool_iterstep(&iter)))) {
64 tselem->used = 0;
65 }
66
67 /* cleanup only after reading file or undo step, and always for
68 * RNA data-blocks view in order to save memory */
69 if (space_outliner->storeflag & SO_TREESTORE_CLEANUP) {
70 space_outliner->storeflag &= ~SO_TREESTORE_CLEANUP;
71
72 BLI_mempool_iternew(ts, &iter);
73 while ((tselem = static_cast<TreeStoreElem *>(BLI_mempool_iterstep(&iter)))) {
74 if (tselem->id == nullptr) {
75 unused++;
76 }
77 }
78
79 if (unused) {
80 if (BLI_mempool_len(ts) == unused) {
82 space_outliner->treestore = nullptr;
83 space_outliner->runtime->tree_hash = nullptr;
84 }
85 else {
86 TreeStoreElem *tsenew;
88 sizeof(TreeStoreElem), BLI_mempool_len(ts) - unused, 512, BLI_MEMPOOL_ALLOW_ITER);
89 BLI_mempool_iternew(ts, &iter);
90 while ((tselem = static_cast<TreeStoreElem *>(BLI_mempool_iterstep(&iter)))) {
91 if (tselem->id) {
92 tsenew = static_cast<TreeStoreElem *>(BLI_mempool_alloc(new_ts));
93 *tsenew = *tselem;
94 }
95 }
97 space_outliner->treestore = new_ts;
98 if (space_outliner->runtime->tree_hash) {
99 /* update hash table to fix broken pointers */
100 space_outliner->runtime->tree_hash->rebuild_from_treestore(*space_outliner->treestore);
101 }
102 }
103 }
104 }
105 else if (space_outliner->runtime->tree_hash) {
106 space_outliner->runtime->tree_hash->clear_used();
107 }
108 }
109}
110
112 SpaceOutliner *space_outliner, TreeElement *te, ID *id, short type, short nr)
113{
114 if (space_outliner->treestore == nullptr) {
115 /* If treestore was not created in `readfile.cc`, create it here. */
116 space_outliner->treestore = BLI_mempool_create(
117 sizeof(TreeStoreElem), 1, 512, BLI_MEMPOOL_ALLOW_ITER);
118 }
119 if (space_outliner->runtime->tree_hash == nullptr) {
120 space_outliner->runtime->tree_hash = treehash::TreeHash::create_from_treestore(
121 *space_outliner->treestore);
122 }
123
124 /* find any unused tree element in treestore and mark it as used
125 * (note that there may be multiple unused elements in case of linked objects) */
126 TreeStoreElem *tselem = space_outliner->runtime->tree_hash->lookup_unused(type, nr, id);
127 if (tselem) {
128 te->store_elem = tselem;
129 tselem->used = 1;
130 return;
131 }
132
133 /* add 1 element to treestore */
134 tselem = static_cast<TreeStoreElem *>(BLI_mempool_alloc(space_outliner->treestore));
135 tselem->type = type;
136 tselem->nr = type ? nr : 0;
137 tselem->id = id;
138 tselem->used = 0;
139 tselem->flag = TSE_CLOSED;
140 te->store_elem = tselem;
141 space_outliner->runtime->tree_hash->add_element(*tselem);
142}
143
145
146/* -------------------------------------------------------------------- */
149
156
158{
159 outliner_free_tree(&space_outliner->tree);
160 outliner_storage_cleanup(space_outliner);
161}
162
164{
165 BLI_assert(BLI_findindex(parent_subtree, element) > -1);
166 BLI_remlink(parent_subtree, element);
167
168 outliner_free_tree(&element->subtree);
169
170 if (element->flag & TE_FREE_NAME) {
171 MEM_freeN(element->name);
172 }
173 element->abstract_element = nullptr;
174 MEM_delete(element);
175}
176
177/* ********************************************************* */
178
179/* -------------------------------------------------------- */
180
182{
183 int exclude_flags = outliner_exclude_filter_get(space_outliner);
184 /* Need to rebuild tree to re-apply filter if select/active changed while filtering based on
185 * select/active. */
187}
188
189#ifdef WITH_FREESTYLE
190static void outliner_add_line_styles(SpaceOutliner *space_outliner,
191 ListBase *lb,
192 Scene *sce,
193 TreeElement *te)
194{
195 ViewLayer *view_layer;
196 FreestyleLineSet *lineset;
197
198 for (view_layer = sce->view_layers.first; view_layer; view_layer = view_layer->next) {
199 for (lineset = view_layer->freestyle_config.linesets.first; lineset; lineset = lineset->next) {
200 FreestyleLineStyle *linestyle = lineset->linestyle;
201 if (linestyle) {
202 linestyle->id.tag |= ID_TAG_DOIT;
203 }
204 }
205 }
206 for (view_layer = sce->view_layers.first; view_layer; view_layer = view_layer->next) {
207 for (lineset = view_layer->freestyle_config.linesets.first; lineset; lineset = lineset->next) {
208 FreestyleLineStyle *linestyle = lineset->linestyle;
209 if (linestyle) {
210 if (!(linestyle->id.tag & ID_TAG_DOIT)) {
211 continue;
212 }
213 linestyle->id.tag &= ~ID_TAG_DOIT;
215 space_outliner, lb, reinterpret_cast<ID *>(linestyle), nullptr, te, TSE_SOME_ID, 0);
216 }
217 }
218 }
219}
220#endif
221
223 ListBase *lb,
224 ID *owner_id,
225 void *create_data,
226 TreeElement *parent,
227 short type,
228 short index,
229 const bool expand)
230{
231 if (!space_outliner->runtime || !space_outliner->runtime->tree_display) {
233 return nullptr;
234 }
235
236 return space_outliner->runtime->tree_display->add_element(
237 lb, owner_id, create_data, parent, type, index, expand);
238}
239
241 ID *owner_id,
242 void *create_data,
243 TreeElement *parent,
244 short type,
245 short index,
246 const bool expand)
247{
248 /* Pointer to store in #TreeStoreElem.id to identify the element over rebuilds and reconstruct it
249 * on file read. */
250 /* FIXME: This is may be an arbitrary void pointer that is cast to an ID pointer. Could be a
251 * temporary stack pointer even. Often works reliably enough at runtime, and file reading handles
252 * cases where data can't be reconstructed just fine (pointer is null'ed). This is still
253 * completely type unsafe and error-prone. */
254 ID *persistent_dataptr = owner_id ? owner_id : static_cast<ID *>(create_data);
255
256 if ((owner_id == nullptr) && ELEM(type, TSE_RNA_STRUCT, TSE_RNA_PROPERTY, TSE_RNA_ARRAY_ELEM)) {
257 persistent_dataptr = static_cast<ID *>(((PointerRNA *)create_data)->data);
258 }
259
260 /* exceptions */
261 if (ELEM(type, TSE_ID_BASE)) {
262 /* pass */
263 }
264 else if (ELEM(type, TSE_GENERIC_LABEL)) {
265 persistent_dataptr = nullptr;
266 }
267 else if (persistent_dataptr == nullptr) {
268 return nullptr;
269 }
270
271 if (type == TSE_SOME_ID) {
272 /* Real ID, ensure we do not get non-outliner ID types here... */
273 BLI_assert(TREESTORE_ID_TYPE(owner_id));
274 }
275
276 TreeElement *te = MEM_new<TreeElement>(__func__);
277 /* add to the visual tree */
278 BLI_addtail(lb, te);
279 /* add to the storage */
280 check_persistent(&space_outliner_, te, persistent_dataptr, type, index);
281 TreeStoreElem *tselem = TREESTORE(te);
282
283 /* if we are searching for something expand to see child elements */
285 tselem->flag |= TSE_CHILDSEARCH;
286 }
287
288 te->parent = parent;
289 te->index = index; /* For data arrays. */
290
291 /* New inheritance based element representation. Not all element types support this yet,
292 * eventually it should replace #TreeElement entirely. */
293 te->abstract_element = AbstractTreeElement::create_from_type(type, *te, owner_id, create_data);
294 if (te->abstract_element) {
295 /* Element types ported to the new design are expected to have their name set at this point! */
296 BLI_assert(te->name != nullptr);
297
298 /* Let the new element inherit the tree display that creates this current tree. */
299 te->abstract_element->display_ = this;
300 }
301
303 /* pass */
304 }
306 /* pass */
307 }
309 /* pass */
310 }
311 else if (ELEM(type, TSE_ACTION_SLOT)) {
312 /* pass */
313 }
314 else if (ELEM(type, TSE_GP_LAYER, TSE_GREASE_PENCIL_NODE)) {
315 /* pass */
316 }
318 /* pass */
319 }
320 else if (ELEM(type, TSE_ID_BASE, TSE_GENERIC_LABEL)) {
321 /* pass */
322 }
323 else if (ELEM(type, TSE_BONE, TSE_EBONE)) {
324 /* pass */
325 }
327 /* pass */
328 }
329 else if (ELEM(type, TSE_DEFGROUP, TSE_DEFGROUP_BASE)) {
330 /* pass */
331 }
332 else if (type == TSE_LINKED_PSYS) {
333 /* pass */
334 }
335 else if (ELEM(type, TSE_CONSTRAINT, TSE_CONSTRAINT_BASE)) {
336 /* pass */
337 }
338 else if (ELEM(type, TSE_POSE_BASE, TSE_POSE_CHANNEL)) {
339 /* pass */
340 }
342 /* pass */
343 }
344 else if (ELEM(type, TSE_R_LAYER, TSE_R_LAYER_BASE)) {
345 /* pass */
346 }
347 else if (ELEM(type, TSE_MODIFIER, TSE_MODIFIER_BASE)) {
348 /* pass */
349 }
350 else if (type == TSE_LINKED_NODE_TREE) {
351 /* pass */
352 }
353 else if (type == TSE_LINKED_OB) {
354 /* pass */
355 }
356 else if (type == TSE_SOME_ID) {
357 BLI_assert_msg(te->abstract_element != nullptr,
358 "Expected this ID type to be ported to new Outliner tree-element design");
359 }
360 else if (ELEM(type,
364 {
365 BLI_assert_msg(te->abstract_element != nullptr,
366 "Expected override types to be ported to new Outliner tree-element design");
367 }
368 else {
369 /* Other cases must be caught above. */
370 BLI_assert(TSE_IS_REAL_ID(tselem));
371 BLI_assert_msg(te->abstract_element != nullptr,
372 "Element type should use `AbstractTreeElement` to for correct initialization "
373 "of its `TreeElement` data");
374
375 /* The new type design sets the name already, don't override that here. We need to figure out
376 * how to deal with the idcode for non-TSE_SOME_ID types still. Some rely on it... */
377 te->idcode = GS(owner_id->name);
378 }
379
380 if (!expand) {
381 /* Pass */
382 }
383 else if (te->abstract_element) {
385 }
386 /* Only #TSE_ID_BASE isn't ported to use the abstract elements design yet. */
387 else if (!ELEM(type, TSE_ID_BASE)) {
388 BLI_assert_msg(false, "Element type should use `AbstractTreeElement`");
389 }
390
391 return te;
392}
393
394/* ======================================================= */
395
397{
398 te->name = BKE_collection_ui_name_get(collection);
399 te->directdata = collection;
400}
401
403 ListBase *tree,
404 Collection *collection,
405 TreeElement *parent)
406{
407 LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
409 space_outliner, tree, reinterpret_cast<ID *>(cob->ob), nullptr, parent, TSE_SOME_ID, 0);
410 }
411}
412
414 Collection *collection,
415 TreeElement *ten)
416{
417 outliner_add_collection_init(ten, collection);
418
419 LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
421 space_outliner, &ten->subtree, &child->collection->id, nullptr, ten, TSE_SOME_ID, 0);
422 }
423
424 if (space_outliner->outlinevis != SO_SCENES) {
425 outliner_add_collection_objects(space_outliner, &ten->subtree, collection, ten);
426 }
427
428 return ten;
429}
430
432
433/* ======================================================= */
434/* Generic Tree Building helpers - order these are called is top to bottom */
435
436/* -------------------------------------------------------------------- */
439
440struct tTreeSort {
443 const char *name;
444 short idcode;
445};
446
447/* alphabetical comparator, trying to put objects first */
448static int treesort_alpha_ob(const void *v1, const void *v2)
449{
450 const tTreeSort *x1 = static_cast<const tTreeSort *>(v1);
451 const tTreeSort *x2 = static_cast<const tTreeSort *>(v2);
452
453 /* first put objects last (hierarchy) */
454 int comp = (x1->idcode == ID_OB);
455 if (x2->idcode == ID_OB) {
456 comp += 2;
457 }
458
459 if (comp == 1) {
460 return 1;
461 }
462 if (comp == 2) {
463 return -1;
464 }
465 if (comp == 3) {
466 /* Among objects first come the ones in the collection, followed by the ones not on it.
467 * This way we can have the dashed lines in a separate style connecting the former. */
469 {
470 return (x1->te->flag & TE_CHILD_NOT_IN_COLLECTION) ? 1 : -1;
471 }
472
473 comp = BLI_strcasecmp_natural(x1->name, x2->name);
474
475 if (comp > 0) {
476 return 1;
477 }
478 if (comp < 0) {
479 return -1;
480 }
481 return 0;
482 }
483 return 0;
484}
485
486/* Move children that are not in the collection to the end of the list. */
487static int treesort_child_not_in_collection(const void *v1, const void *v2)
488{
489 const tTreeSort *x1 = static_cast<const tTreeSort *>(v1);
490 const tTreeSort *x2 = static_cast<const tTreeSort *>(v2);
491
492 /* Among objects first come the ones in the collection, followed by the ones not on it.
493 * This way we can have the dashed lines in a separate style connecting the former. */
495 return (x1->te->flag & TE_CHILD_NOT_IN_COLLECTION) ? 1 : -1;
496 }
497 return 0;
498}
499
500/* alphabetical comparator */
501static int treesort_alpha(const void *v1, const void *v2)
502{
503 const tTreeSort *x1 = static_cast<const tTreeSort *>(v1);
504 const tTreeSort *x2 = static_cast<const tTreeSort *>(v2);
505
506 int comp = BLI_strcasecmp_natural(x1->name, x2->name);
507
508 if (comp > 0) {
509 return 1;
510 }
511 if (comp < 0) {
512 return -1;
513 }
514 return 0;
515}
516
517/* this is nice option for later? doesn't look too useful... */
518#if 0
519static int treesort_obtype_alpha(const void *v1, const void *v2)
520{
521 const tTreeSort *x1 = v1, *x2 = v2;
522
523 /* first put objects last (hierarchy) */
524 if (x1->idcode == ID_OB && x2->idcode != ID_OB) {
525 return 1;
526 }
527 else if (x2->idcode == ID_OB && x1->idcode != ID_OB) {
528 return -1;
529 }
530 else {
531 /* 2nd we check ob type */
532 if (x1->idcode == ID_OB && x2->idcode == ID_OB) {
533 if (((Object *)x1->id)->type > ((Object *)x2->id)->type) {
534 return 1;
535 }
536 else if (((Object *)x1->id)->type > ((Object *)x2->id)->type) {
537 return -1;
538 }
539 else {
540 return 0;
541 }
542 }
543 else {
544 int comp = BLI_strcasecmp_natural(x1->name, x2->name);
545
546 if (comp > 0) {
547 return 1;
548 }
549 else if (comp < 0) {
550 return -1;
551 }
552 return 0;
553 }
554 }
555}
556#endif
557
558/* sort happens on each subtree individual */
559static void outliner_sort(ListBase *lb)
560{
561 TreeElement *last_te = static_cast<TreeElement *>(lb->last);
562 if (last_te == nullptr) {
563 return;
564 }
565 TreeStoreElem *last_tselem = TREESTORE(last_te);
566
567 /* Sorting rules; only object lists, ID lists, or deform-groups. */
568 if (ELEM(last_tselem->type, TSE_DEFGROUP, TSE_ID_BASE) ||
569 ((last_tselem->type == TSE_SOME_ID) && (last_te->idcode == ID_OB)))
570 {
571 int totelem = BLI_listbase_count(lb);
572
573 if (totelem > 1) {
574 tTreeSort *tear = MEM_malloc_arrayN<tTreeSort>(totelem, "tree sort array");
575 tTreeSort *tp = tear;
576 int skip = 0;
577
578 LISTBASE_FOREACH (TreeElement *, te, lb) {
579 TreeStoreElem *tselem = TREESTORE(te);
580 tp->te = te;
581 tp->name = te->name;
582 tp->idcode = te->idcode;
583
584 if (!ELEM(tselem->type, TSE_SOME_ID, TSE_DEFGROUP)) {
585 tp->idcode = 0; /* Don't sort this. */
586 }
587 if (ELEM(tselem->type, TSE_ID_BASE, TSE_DEFGROUP)) {
588 tp->idcode = 1; /* Do sort this. */
589 }
590
591 tp->id = tselem->id;
592 tp++;
593 }
594
595 /* just sort alphabetically */
596 if (tear->idcode == 1) {
597 qsort(tear, totelem, sizeof(tTreeSort), treesort_alpha);
598 }
599 else {
600 /* keep beginning of list */
601 for (tp = tear, skip = 0; skip < totelem; skip++, tp++) {
602 if (tp->idcode) {
603 break;
604 }
605 }
606
607 if (skip < totelem) {
608 qsort(tear + skip, totelem - skip, sizeof(tTreeSort), treesort_alpha_ob);
609 }
610 }
611
613 tp = tear;
614 while (totelem--) {
615 BLI_addtail(lb, tp->te);
616 tp++;
617 }
618 MEM_freeN(tear);
619 }
620 }
621
622 LISTBASE_FOREACH (TreeElement *, te_iter, lb) {
623 outliner_sort(&te_iter->subtree);
624 }
625}
626
628{
629 TreeElement *last_te = static_cast<TreeElement *>(lb->last);
630 if (last_te == nullptr) {
631 return;
632 }
633 TreeStoreElem *last_tselem = TREESTORE(last_te);
634
635 /* Sorting rules: only object lists. */
636 if ((last_tselem->type == TSE_SOME_ID) && (last_te->idcode == ID_OB)) {
637 int totelem = BLI_listbase_count(lb);
638
639 if (totelem > 1) {
640 tTreeSort *tear = MEM_malloc_arrayN<tTreeSort>(totelem, "tree sort array");
641 tTreeSort *tp = tear;
642
643 LISTBASE_FOREACH (TreeElement *, te, lb) {
644 TreeStoreElem *tselem = TREESTORE(te);
645 tp->te = te;
646 tp->name = te->name;
647 tp->idcode = te->idcode;
648 tp->id = tselem->id;
649 tp++;
650 }
651
652 qsort(tear, totelem, sizeof(tTreeSort), treesort_child_not_in_collection);
653
655 tp = tear;
656 while (totelem--) {
657 BLI_addtail(lb, tp->te);
658 tp++;
659 }
660 MEM_freeN(tear);
661 }
662 }
663
664 LISTBASE_FOREACH (TreeElement *, te_iter, lb) {
665 outliner_collections_children_sort(&te_iter->subtree);
666 }
667}
668
670
671/* -------------------------------------------------------------------- */
674
679
685 ARegion *region,
687{
688 View2D *v2d = &region->v2d;
689
690 if (focus->tselem != nullptr) {
691 outliner_set_coordinates(region, space_outliner);
692
693 TreeElement *te_new = outliner_find_tree_element(&space_outliner->tree, focus->tselem);
694
695 if (te_new != nullptr) {
696 int ys_new = te_new->ys;
697 int ys_old = focus->ys;
698
699 float y_move = std::min(float(ys_new - ys_old), -v2d->cur.ymax);
700 BLI_rctf_translate(&v2d->cur, 0, y_move);
701 }
702 else {
703 return;
704 }
705 }
706}
707
712
714{
715 TreeStoreElem *tselem = TREESTORE(te);
716 return ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB));
717}
718
723 const SpaceOutliner *space_outliner,
724 TreeElement *te,
725 const float limit,
726 bool (*callback_test)(TreeElement *))
727{
728 if (callback_test(te)) {
729 return te;
730 }
731
732 if (TSELEM_OPEN(te->store_elem, space_outliner)) {
733 LISTBASE_FOREACH (TreeElement *, te_iter, &te->subtree) {
735 space_outliner, te_iter, limit, callback_test);
736 if (te_sub != nullptr) {
737 return te_sub;
738 }
739 }
740 }
741
742 return nullptr;
743}
744
756 const float view_co,
757 const float view_co_limit)
758{
759 TreeElement *te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_co);
760
761 bool (*callback_test)(TreeElement *);
762 if ((space_outliner->outlinevis == SO_VIEW_LAYER) &&
763 (space_outliner->filter & SO_FILTER_NO_COLLECTION))
764 {
765 callback_test = test_object_callback;
766 }
767 else {
768 callback_test = test_collection_callback;
769 }
770
771 while (te != nullptr) {
773 space_outliner, te, view_co_limit, callback_test);
774 if (te_sub != nullptr) {
775 /* Skip the element if it was not visible to start with. */
776 if (te->ys + UI_UNIT_Y > view_co_limit) {
777 return te_sub;
778 }
779 return nullptr;
780 }
781
782 if (te->next) {
783 te = te->next;
784 continue;
785 }
786
787 if (te->parent == nullptr) {
788 break;
789 }
790
791 while (te->parent) {
792 if (te->parent->next) {
793 te = te->parent->next;
794 break;
795 }
796 te = te->parent;
797 }
798 }
799
800 return nullptr;
801}
802
811 ARegion *region,
813{
814 float limit = region->v2d.cur.ymin;
815
816 outliner_set_coordinates(region, space_outliner);
817
819 space_outliner, region->v2d.cur.ymax, limit);
820
821 if (te != nullptr) {
822 focus->tselem = TREESTORE(te);
823 focus->ys = te->ys;
824 }
825 else {
826 focus->tselem = nullptr;
827 }
828}
829
830static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner)
831{
832 int exclude_filter = space_outliner->filter & ~SO_FILTER_OB_STATE;
833
834 if ((space_outliner->search_string[0] != 0) && ED_outliner_support_searching(space_outliner)) {
835 exclude_filter |= SO_FILTER_SEARCH;
836 }
837 else {
838 exclude_filter &= ~SO_FILTER_SEARCH;
839 }
840
841 /* Let's have this for the collection options at first. */
842 if (!SUPPORT_FILTER_OUTLINER(space_outliner)) {
843 return (exclude_filter & SO_FILTER_SEARCH);
844 }
845
846 if (space_outliner->filter & SO_FILTER_NO_OBJECT) {
847 exclude_filter |= SO_FILTER_OB_TYPE;
848 }
849
850 switch (space_outliner->filter_state) {
852 exclude_filter |= SO_FILTER_OB_STATE_VISIBLE;
853 break;
855 exclude_filter |= SO_FILTER_OB_STATE_SELECTED;
856 break;
858 exclude_filter |= SO_FILTER_OB_STATE_ACTIVE;
859 break;
861 exclude_filter |= SO_FILTER_OB_STATE_SELECTABLE;
862 break;
863 }
864
865 return exclude_filter;
866}
867
868static bool outliner_element_visible_get(const Scene *scene,
869 ViewLayer *view_layer,
870 TreeElement *te,
871 const int exclude_filter)
872{
873 if ((exclude_filter & SO_FILTER_ANY) == 0) {
874 return true;
875 }
876
877 TreeStoreElem *tselem = TREESTORE(te);
878 if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) {
879 if ((exclude_filter & SO_FILTER_OB_TYPE) == SO_FILTER_OB_TYPE) {
880 return false;
881 }
882
883 Object *ob = (Object *)tselem->id;
884 Base *base = (Base *)te->directdata;
885 BLI_assert((base == nullptr) || (base->object == ob));
886
887 if (exclude_filter & SO_FILTER_OB_TYPE) {
888 switch (ob->type) {
889 case OB_MESH:
890 if (exclude_filter & SO_FILTER_NO_OB_MESH) {
891 return false;
892 }
893 break;
894 case OB_ARMATURE:
895 if (exclude_filter & SO_FILTER_NO_OB_ARMATURE) {
896 return false;
897 }
898 break;
899 case OB_EMPTY:
900 if (exclude_filter & SO_FILTER_NO_OB_EMPTY) {
901 return false;
902 }
903 break;
904 case OB_LAMP:
905 if (exclude_filter & SO_FILTER_NO_OB_LAMP) {
906 return false;
907 }
908 break;
909 case OB_CAMERA:
910 if (exclude_filter & SO_FILTER_NO_OB_CAMERA) {
911 return false;
912 }
913 break;
914 case OB_GREASE_PENCIL:
915 if (exclude_filter & SO_FILTER_NO_OB_GREASE_PENCIL) {
916 return false;
917 }
918 break;
919 default:
920 if (exclude_filter & SO_FILTER_NO_OB_OTHERS) {
921 return false;
922 }
923 break;
924 }
925 }
926
927 if (exclude_filter & SO_FILTER_OB_STATE) {
928 if (base == nullptr) {
929 BKE_view_layer_synced_ensure(scene, view_layer);
930 base = BKE_view_layer_base_find(view_layer, ob);
931
932 if (base == nullptr) {
933 return false;
934 }
935 }
936
937 bool is_visible = true;
938 if (exclude_filter & SO_FILTER_OB_STATE_VISIBLE) {
940 is_visible = false;
941 }
942 }
943 else if (exclude_filter & SO_FILTER_OB_STATE_SELECTED) {
944 if ((base->flag & BASE_SELECTED) == 0) {
945 is_visible = false;
946 }
947 }
948 else if (exclude_filter & SO_FILTER_OB_STATE_SELECTABLE) {
949 if ((base->flag & BASE_SELECTABLE) == 0) {
950 is_visible = false;
951 }
952 }
953 else {
954 BLI_assert(exclude_filter & SO_FILTER_OB_STATE_ACTIVE);
955 BKE_view_layer_synced_ensure(scene, view_layer);
956 if (base != BKE_view_layer_active_base_get(view_layer)) {
957 is_visible = false;
958 }
959 }
960
961 if (exclude_filter & SO_FILTER_OB_STATE_INVERSE) {
962 is_visible = !is_visible;
963 }
964
965 return is_visible;
966 }
967
968 if ((te->parent != nullptr) && (TREESTORE(te->parent)->type == TSE_SOME_ID) &&
969 (te->parent->idcode == ID_OB))
970 {
971 if (exclude_filter & SO_FILTER_NO_CHILDREN) {
972 return false;
973 }
974 }
975 }
976 else if ((te->parent != nullptr) && (TREESTORE(te->parent)->type == TSE_SOME_ID) &&
977 (te->parent->idcode == ID_OB))
978 {
979 if (exclude_filter & SO_FILTER_NO_OB_CONTENT) {
980 return false;
981 }
982 }
983
984 return true;
985}
986
987static bool outliner_filter_has_name(TreeElement *te, const char *name, int flags)
988{
989 /* Use `fnmatch` for shell-style globing.
990 * - Case-insensitive (optionally).
991 * - Don't handle escape characters as "special" characters are not expected in names.
992 * Unlike shell input - `\` should be treated like any other character.
993 */
994 int fn_flag = FNM_NOESCAPE;
995
996 if ((flags & SO_FIND_CASE_SENSITIVE) == 0) {
997 fn_flag |= FNM_CASEFOLD;
998 }
999
1000 return fnmatch(name, te->name, fn_flag) == 0;
1001}
1002
1004{
1005 TreeStoreElem *tselem = TREESTORE(te);
1006
1007 if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) {
1008 return true;
1009 }
1010
1011 /* Collection instance datablocks should not be extracted. */
1012 if (outliner_is_collection_tree_element(te) && !(te->parent && te->parent->idcode == ID_OB)) {
1013 return true;
1014 }
1015
1016 return false;
1017}
1018
1020 ListBase *parent_subtree)
1021{
1022 TreeElement *te_next = element->next;
1023
1025 TreeElement *te_prev = nullptr;
1026 for (TreeElement *te = static_cast<TreeElement *>(element->subtree.last); te; te = te_prev) {
1027 te_prev = te->prev;
1028
1030 continue;
1031 }
1032
1033 te_next = te;
1034 BLI_remlink(&element->subtree, te);
1035 BLI_insertlinkafter(parent_subtree, element->prev, te);
1036 te->parent = element->parent;
1037 }
1038 }
1039
1040 outliner_free_tree_element(element, parent_subtree);
1041 return te_next;
1042}
1043
1044static int outliner_filter_subtree(SpaceOutliner *space_outliner,
1045 const Scene *scene,
1046 ViewLayer *view_layer,
1047 ListBase *lb,
1048 const char *search_string,
1049 const int exclude_filter)
1050{
1051 TreeElement *te, *te_next;
1052 TreeStoreElem *tselem;
1053
1054 for (te = static_cast<TreeElement *>(lb->first); te; te = te_next) {
1055 te_next = te->next;
1056 if (outliner_element_visible_get(scene, view_layer, te, exclude_filter) == false) {
1057 /* Don't free the tree, but extract the children from the parent and add to this tree. */
1058 /* This also needs filtering the subtree prior (see #69246). */
1060 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter);
1062 continue;
1063 }
1064 if ((exclude_filter & SO_FILTER_SEARCH) == 0) {
1065 /* Filter subtree too. */
1067 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter);
1068 continue;
1069 }
1070
1071 if (!outliner_filter_has_name(te, search_string, space_outliner->search_flags)) {
1072 /* item isn't something we're looking for, but...
1073 * - if the subtree is expanded, check if there are any matches that can be easily found
1074 * so that searching for "cu" in the default scene will still match the Cube
1075 * - otherwise, we can't see within the subtree and the item doesn't match,
1076 * so these can be safely ignored (i.e. the subtree can get freed)
1077 */
1078 tselem = TREESTORE(te);
1079
1080 /* flag as not a found item */
1081 tselem->flag &= ~TSE_SEARCHMATCH;
1082
1083 if (!TSELEM_OPEN(tselem, space_outliner) ||
1085 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter) == 0)
1086 {
1088 }
1089 }
1090 else {
1091 tselem = TREESTORE(te);
1092
1093 /* flag as a found item - we can then highlight it */
1094 tselem->flag |= TSE_SEARCHMATCH;
1095
1096 /* filter subtree too */
1098 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter);
1099 }
1100 }
1101
1102 /* if there are still items in the list, that means that there were still some matches */
1103 return (BLI_listbase_is_empty(lb) == false);
1104}
1105
1106static void outliner_filter_tree(SpaceOutliner *space_outliner,
1107 const Scene *scene,
1108 ViewLayer *view_layer)
1109{
1110 char search_buff[sizeof(SpaceOutliner::search_string) + 2];
1111 const char *search_string;
1112
1113 const int exclude_filter = outliner_exclude_filter_get(space_outliner);
1114
1115 if (exclude_filter == 0) {
1116 return;
1117 }
1118
1119 if (space_outliner->search_flags & SO_FIND_COMPLETE) {
1120 search_string = space_outliner->search_string;
1121 }
1122 else {
1123 /* Implicitly add heading/trailing wildcards if needed. */
1124 BLI_strncpy_ensure_pad(search_buff, space_outliner->search_string, '*', sizeof(search_buff));
1125 search_string = search_buff;
1126 }
1127
1129 space_outliner, scene, view_layer, &space_outliner->tree, search_string, exclude_filter);
1130}
1131
1133{
1134 ID *id_iter;
1135 FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
1136 id_iter->newid = nullptr;
1137 }
1139}
1140
1142
1143/* -------------------------------------------------------------------- */
1146
1148 WorkSpace *workspace,
1149 Scene *scene,
1150 ViewLayer *view_layer,
1151 SpaceOutliner *space_outliner,
1152 ARegion *region)
1153{
1154 /* Are we looking for something - we want to tag parents to filter child matches
1155 * - NOT in data-blocks view - searching all data-blocks takes way too long to be useful
1156 * - this variable is only set once per tree build */
1157 if (space_outliner->search_string[0] != 0 && space_outliner->outlinevis != SO_DATA_API &&
1158 ED_outliner_support_searching(space_outliner))
1159 {
1160 space_outliner->search_flags |= SO_SEARCH_RECURSIVE;
1161 }
1162 else {
1163 space_outliner->search_flags &= ~SO_SEARCH_RECURSIVE;
1164 }
1165
1166 if (space_outliner->runtime->tree_hash && (space_outliner->storeflag & SO_TREESTORE_REBUILD) &&
1167 space_outliner->treestore)
1168 {
1169 space_outliner->runtime->tree_hash->rebuild_from_treestore(*space_outliner->treestore);
1170 }
1171 space_outliner->storeflag &= ~SO_TREESTORE_REBUILD;
1172
1173 if (region->runtime->do_draw & RGN_DRAW_NO_REBUILD) {
1174 BLI_assert_msg(space_outliner->runtime->tree_display != nullptr,
1175 "Skipping rebuild before tree was built properly, a full redraw should be "
1176 "triggered instead");
1177 return;
1178 }
1179
1180 /* Enable for benchmarking. Starts a timer, results will be printed on function exit. */
1181 // SCOPED_TIMER("Outliner Rebuild");
1182 // SCOPED_TIMER_AVERAGED("Outliner Rebuild");
1183
1185 outliner_store_scrolling_position(space_outliner, region, &focus);
1186
1187 outliner_free_tree(&space_outliner->tree);
1188 outliner_storage_cleanup(space_outliner);
1189
1191 space_outliner->outlinevis, *space_outliner);
1192
1193 /* All tree displays should be created as sub-classes of AbstractTreeDisplay. */
1194 BLI_assert(space_outliner->runtime->tree_display != nullptr);
1195
1196 TreeSourceData source_data{*mainvar, *workspace, *scene, *view_layer};
1197 space_outliner->tree = space_outliner->runtime->tree_display->build_tree(source_data);
1198
1199 if ((space_outliner->flag & SO_SKIP_SORT_ALPHA) == 0) {
1200 outliner_sort(&space_outliner->tree);
1201 }
1202 else if ((space_outliner->filter & SO_FILTER_NO_CHILDREN) == 0) {
1203 /* We group the children that are in the collection before the ones that are not.
1204 * This way we can try to draw them in a different style altogether.
1205 * We also have to respect the original order of the elements in case alphabetical
1206 * sorting is not enabled. This keep object data and modifiers before its children. */
1207 outliner_collections_children_sort(&space_outliner->tree);
1208 }
1209
1210 outliner_filter_tree(space_outliner, scene, view_layer);
1211 outliner_restore_scrolling_position(space_outliner, region, &focus);
1212
1213 /* `ID.newid` pointer is abused when building tree, DO NOT call #BKE_main_id_newptr_and_tag_clear
1214 * as this expects valid IDs in this pointer, not random unknown data. */
1216}
1217
1219
1220} // namespace blender::ed::outliner
const char * BKE_collection_ui_name_get(Collection *collection)
void BKE_view_layer_synced_ensure(const Scene *scene, ViewLayer *view_layer)
Base * BKE_view_layer_active_base_get(ViewLayer *view_layer)
Base * BKE_view_layer_base_find(ViewLayer *view_layer, Object *ob)
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:583
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:577
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define BLI_INLINE
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE void BLI_listbase_clear(ListBase *lb)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_insertlinkafter(ListBase *listbase, void *vprevlink, void *vnewlink) ATTR_NONNULL(1)
Definition listbase.cc:332
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
void * BLI_mempool_alloc(BLI_mempool *pool) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1)
void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL()
void * BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
@ BLI_MEMPOOL_ALLOW_ITER
BLI_mempool * BLI_mempool_create(unsigned int esize, unsigned int elem_num, unsigned int pchunk, unsigned int flag) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL
int BLI_mempool_len(const BLI_mempool *pool) ATTR_NONNULL(1)
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
void BLI_rctf_translate(struct rctf *rect, float x, float y)
Definition rct.cc:573
int char char int int int BLI_strcasecmp_natural(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char char * BLI_strncpy_ensure_pad(char *__restrict dst, const char *__restrict src, char pad, size_t dst_maxncpy) ATTR_NONNULL(1
#define ELEM(...)
@ ID_TAG_DOIT
Definition DNA_ID.h:1036
struct ID ID
@ ID_OB
Object groups, one object can be in many groups at once.
@ BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT
struct FreestyleLineStyle FreestyleLineStyle
@ OB_EMPTY
@ OB_CAMERA
@ OB_GREASE_PENCIL
@ OB_ARMATURE
@ OB_LAMP
@ OB_MESH
struct Object Object
#define TSE_IS_REAL_ID(_tse)
@ TSE_STRIP_DUP
@ TSE_BONE_COLLECTION
@ TSE_ACTION_SLOT
@ TSE_POSE_CHANNEL
@ TSE_CONSTRAINT_BASE
@ TSE_LIBRARY_OVERRIDE_OPERATION
@ TSE_STRIP
@ TSE_MODIFIER_BASE
@ TSE_GP_LAYER
@ TSE_RNA_ARRAY_ELEM
@ TSE_GPENCIL_EFFECT
@ TSE_LINKED_NODE_TREE
@ TSE_STRIP_DATA
@ TSE_VIEW_COLLECTION_BASE
@ TSE_ANIM_DATA
@ TSE_LIBRARY_OVERRIDE
@ TSE_RNA_PROPERTY
@ TSE_LIBRARY_OVERRIDE_BASE
@ TSE_EBONE
@ TSE_NLA_TRACK
@ TSE_BONE
@ TSE_LINKED_PSYS
@ TSE_DEFGROUP_BASE
@ TSE_CONSTRAINT
@ TSE_SCENE_COLLECTION_BASE
@ TSE_R_LAYER_BASE
@ TSE_LAYER_COLLECTION
@ TSE_GREASE_PENCIL_NODE
@ TSE_GENERIC_LABEL
@ TSE_GPENCIL_EFFECT_BASE
@ TSE_LINKED_OB
@ TSE_NLA
@ TSE_ID_BASE
@ TSE_SOME_ID
@ TSE_DRIVER_BASE
@ TSE_MODIFIER
@ TSE_BONE_COLLECTION_BASE
@ TSE_R_LAYER
@ TSE_RNA_STRUCT
@ TSE_POSE_BASE
@ TSE_DEFGROUP
@ TSE_CHILDSEARCH
@ TSE_CLOSED
@ TSE_SEARCHMATCH
#define BASE_SELECTED(v3d, base)
#define BASE_SELECTABLE(v3d, base)
@ RGN_DRAW_NO_REBUILD
@ SO_FIND_COMPLETE
@ SO_SEARCH_RECURSIVE
@ SO_FIND_CASE_SENSITIVE
#define SO_FILTER_OB_STATE
@ SO_FILTER_OB_STATE_ACTIVE
@ SO_FILTER_NO_OB_MESH
@ SO_FILTER_NO_OB_CAMERA
@ SO_FILTER_NO_CHILDREN
@ SO_FILTER_SEARCH
@ SO_FILTER_NO_OB_CONTENT
@ SO_FILTER_NO_OB_GREASE_PENCIL
@ SO_FILTER_NO_OB_LAMP
@ SO_FILTER_OB_STATE_SELECTABLE
@ SO_FILTER_OB_STATE_INVERSE
@ SO_FILTER_OB_STATE_SELECTED
@ SO_FILTER_OB_STATE_VISIBLE
@ SO_FILTER_NO_OBJECT
@ SO_FILTER_NO_OB_OTHERS
@ SO_FILTER_NO_OB_EMPTY
@ SO_FILTER_NO_COLLECTION
@ SO_FILTER_NO_OB_ARMATURE
#define SO_FILTER_OB_TYPE
@ SO_SKIP_SORT_ALPHA
@ SO_FILTER_OB_SELECTABLE
@ SO_FILTER_OB_SELECTED
@ SO_FILTER_OB_VISIBLE
@ SO_FILTER_OB_ACTIVE
@ SO_TREESTORE_CLEANUP
@ SO_TREESTORE_REBUILD
#define SO_FILTER_ANY
@ SO_DATA_API
@ SO_VIEW_LAYER
@ SO_SCENES
struct SpaceOutliner SpaceOutliner
bool ED_outliner_support_searching(const SpaceOutliner *space_outliner)
Read Guarded memory(de)allocation.
#define UI_UNIT_Y
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const void * element
ATTR_WARN_UNUSED_RESULT const BMVert * v2
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)
static std::unique_ptr< AbstractTreeDisplay > create_from_display_mode(int mode, SpaceOutliner &space_outliner)
static std::unique_ptr< AbstractTreeElement > create_from_type(int type, TreeElement &legacy_te, ID *owner_id, void *create_data)
GPU_SHADER_INTERFACE_INFO(depth_2d_update_iface).smooth(Type fragColor push_constant(Type::float2_t, "extent") .push_constant(Type source_data
KDTree_3d * tree
#define GS(x)
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static TreeElement * outliner_find_first_desired_element_at_y_recursive(const SpaceOutliner *space_outliner, TreeElement *te, const float limit, bool(*callback_test)(TreeElement *))
static void outliner_collections_children_sort(ListBase *lb)
static void outliner_restore_scrolling_position(SpaceOutliner *space_outliner, ARegion *region, OutlinerTreeElementFocus *focus)
bool outliner_is_collection_tree_element(const TreeElement *te)
static bool outliner_element_is_collection_or_object(TreeElement *te)
static TreeElement * outliner_extract_children_from_subtree(TreeElement *element, ListBase *parent_subtree)
void outliner_free_tree_element(TreeElement *element, ListBase *parent_subtree)
static int treesort_child_not_in_collection(const void *v1, const void *v2)
void tree_element_expand(const AbstractTreeElement &tree_element, SpaceOutliner &space_outliner)
static bool test_object_callback(TreeElement *te)
static void outliner_storage_cleanup(SpaceOutliner *space_outliner)
TreeElement * outliner_add_collection_recursive(SpaceOutliner *space_outliner, Collection *collection, TreeElement *ten)
bool outliner_requires_rebuild_on_select_or_active_change(const SpaceOutliner *space_outliner)
static int outliner_filter_subtree(SpaceOutliner *space_outliner, const Scene *scene, ViewLayer *view_layer, ListBase *lb, const char *search_string, const int exclude_filter)
TreeElement * outliner_find_item_at_y(const SpaceOutliner *space_outliner, const ListBase *tree, float view_co_y)
void outliner_free_tree(ListBase *tree)
void outliner_set_coordinates(const ARegion *region, const SpaceOutliner *space_outliner)
static bool outliner_filter_has_name(TreeElement *te, const char *name, int flags)
static int treesort_alpha(const void *v1, const void *v2)
TreeElement * outliner_find_tree_element(ListBase *lb, const TreeStoreElem *store_elem)
static void check_persistent(SpaceOutliner *space_outliner, TreeElement *te, ID *id, short type, short nr)
void outliner_cleanup_tree(SpaceOutliner *space_outliner)
static void outliner_clear_newid_from_main(Main *bmain)
static TreeElement * outliner_find_first_desired_element_at_y(const SpaceOutliner *space_outliner, const float view_co, const float view_co_limit)
BLI_INLINE void outliner_add_collection_init(TreeElement *te, Collection *collection)
BLI_INLINE void outliner_add_collection_objects(SpaceOutliner *space_outliner, ListBase *tree, Collection *collection, TreeElement *parent)
static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner)
static int treesort_alpha_ob(const void *v1, const void *v2)
void outliner_build_tree(Main *mainvar, WorkSpace *workspace, Scene *scene, ViewLayer *view_layer, SpaceOutliner *space_outliner, ARegion *region)
static bool outliner_element_visible_get(const Scene *scene, ViewLayer *view_layer, TreeElement *te, const int exclude_filter)
static void outliner_store_scrolling_position(SpaceOutliner *space_outliner, ARegion *region, OutlinerTreeElementFocus *focus)
static bool test_collection_callback(TreeElement *te)
static void outliner_sort(ListBase *lb)
static void outliner_filter_tree(SpaceOutliner *space_outliner, const Scene *scene, ViewLayer *view_layer)
#define TREESTORE_ID_TYPE(_id)
#define SEARCHING_OUTLINER(sov)
#define SUPPORT_FILTER_OUTLINER(space_outliner_)
#define TREESTORE(a)
#define TSELEM_OPEN(telm, sv)
const char * name
ARegionRuntimeHandle * runtime
short flag
struct Object * object
struct FreestyleLineStyle * linestyle
struct FreestyleLineSet * next
Definition DNA_ID.h:414
int tag
Definition DNA_ID.h:442
char name[258]
Definition DNA_ID.h:432
struct ID * newid
Definition DNA_ID.h:418
void * last
void * first
ListBase view_layers
char search_string[64]
SpaceOutliner_Runtime * runtime
struct BLI_mempool * treestore
struct FreestyleConfig freestyle_config
struct ViewLayer * next
std::unique_ptr< treehash::TreeHash > tree_hash
std::unique_ptr< AbstractTreeDisplay > tree_display
std::unique_ptr< AbstractTreeElement > abstract_element
The data to build the tree from.
float ymax
float ymin
Establish and manage Outliner trees for different display modes.