Blender V4.3
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
9#include <algorithm>
10#include <cmath>
11#include <cstring>
12
13#include "MEM_guardedalloc.h"
14
16
17#include "BLI_blenlib.h"
18#include "BLI_fnmatch.h"
19#include "BLI_listbase.h"
20#include "BLI_mempool.h"
21#include "BLI_utildefines.h"
22
23#include "BKE_layer.hh"
24#include "BKE_main.hh"
25#include "BKE_modifier.hh"
27
28#include "ED_screen.hh"
29
30#include "UI_interface.hh"
31
32#include "outliner_intern.hh"
33#include "tree/common.hh"
34#include "tree/tree_display.hh"
35#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/* -------------------------------------------------------------------- */
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
146/* -------------------------------------------------------------------- */
156
158{
159 outliner_free_tree(&space_outliner->tree);
160 outliner_storage_cleanup(space_outliner);
161}
162
163void outliner_free_tree_element(TreeElement *element, ListBase *parent_subtree)
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((void *)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) {
201 if (linestyle) {
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) {
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_GP_LAYER, TSE_GREASE_PENCIL_NODE)) {
312 /* pass */
313 }
315 /* pass */
316 }
317 else if (ELEM(type, TSE_ID_BASE, TSE_GENERIC_LABEL)) {
318 /* pass */
319 }
320 else if (ELEM(type, TSE_BONE, TSE_EBONE)) {
321 /* pass */
322 }
324 /* pass */
325 }
326 else if (ELEM(type, TSE_DEFGROUP, TSE_DEFGROUP_BASE)) {
327 /* pass */
328 }
329 else if (type == TSE_LINKED_PSYS) {
330 /* pass */
331 }
332 else if (ELEM(type, TSE_CONSTRAINT, TSE_CONSTRAINT_BASE)) {
333 /* pass */
334 }
335 else if (ELEM(type, TSE_POSE_BASE, TSE_POSE_CHANNEL)) {
336 /* pass */
337 }
339 /* pass */
340 }
341 else if (ELEM(type, TSE_R_LAYER, TSE_R_LAYER_BASE)) {
342 /* pass */
343 }
344 else if (ELEM(type, TSE_MODIFIER, TSE_MODIFIER_BASE)) {
345 /* pass */
346 }
347 else if (type == TSE_LINKED_NODE_TREE) {
348 /* pass */
349 }
350 else if (type == TSE_LINKED_OB) {
351 /* pass */
352 }
353 else if (type == TSE_SOME_ID) {
354 BLI_assert_msg(te->abstract_element != nullptr,
355 "Expected this ID type to be ported to new Outliner tree-element design");
356 }
357 else if (ELEM(type,
361 {
362 BLI_assert_msg(te->abstract_element != nullptr,
363 "Expected override types to be ported to new Outliner tree-element design");
364 }
365 else {
366 /* Other cases must be caught above. */
367 BLI_assert(TSE_IS_REAL_ID(tselem));
368 BLI_assert_msg(te->abstract_element != nullptr,
369 "Element type should use `AbstractTreeElement` to for correct initialization "
370 "of its `TreeElement` data");
371
372 /* The new type design sets the name already, don't override that here. We need to figure out
373 * how to deal with the idcode for non-TSE_SOME_ID types still. Some rely on it... */
374 te->idcode = GS(owner_id->name);
375 }
376
377 if (!expand) {
378 /* Pass */
379 }
380 else if (te->abstract_element) {
382 }
383 /* Only #TSE_ID_BASE isn't ported to use the abstract elements design yet. */
384 else if (!ELEM(type, TSE_ID_BASE)) {
385 BLI_assert_msg(false, "Element type should use `AbstractTreeElement`");
386 }
387
388 return te;
389}
390
391/* ======================================================= */
392
394{
395 te->name = BKE_collection_ui_name_get(collection);
396 te->directdata = collection;
397}
398
400 ListBase *tree,
401 Collection *collection,
402 TreeElement *parent)
403{
404 LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
406 space_outliner, tree, reinterpret_cast<ID *>(cob->ob), nullptr, parent, TSE_SOME_ID, 0);
407 }
408}
409
411 Collection *collection,
412 TreeElement *ten)
413{
414 outliner_add_collection_init(ten, collection);
415
416 LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
418 space_outliner, &ten->subtree, &child->collection->id, nullptr, ten, TSE_SOME_ID, 0);
419 }
420
421 if (space_outliner->outlinevis != SO_SCENES) {
422 outliner_add_collection_objects(space_outliner, &ten->subtree, collection, ten);
423 }
424
425 return ten;
426}
427
430/* ======================================================= */
431/* Generic Tree Building helpers - order these are called is top to bottom */
432
433/* -------------------------------------------------------------------- */
437struct tTreeSort {
440 const char *name;
441 short idcode;
442};
443
444/* alphabetical comparator, trying to put objects first */
445static int treesort_alpha_ob(const void *v1, const void *v2)
446{
447 const tTreeSort *x1 = static_cast<const tTreeSort *>(v1);
448 const tTreeSort *x2 = static_cast<const tTreeSort *>(v2);
449
450 /* first put objects last (hierarchy) */
451 int comp = (x1->idcode == ID_OB);
452 if (x2->idcode == ID_OB) {
453 comp += 2;
454 }
455
456 if (comp == 1) {
457 return 1;
458 }
459 if (comp == 2) {
460 return -1;
461 }
462 if (comp == 3) {
463 /* Among objects first come the ones in the collection, followed by the ones not on it.
464 * This way we can have the dashed lines in a separate style connecting the former. */
466 {
467 return (x1->te->flag & TE_CHILD_NOT_IN_COLLECTION) ? 1 : -1;
468 }
469
470 comp = BLI_strcasecmp_natural(x1->name, x2->name);
471
472 if (comp > 0) {
473 return 1;
474 }
475 if (comp < 0) {
476 return -1;
477 }
478 return 0;
479 }
480 return 0;
481}
482
483/* Move children that are not in the collection to the end of the list. */
484static int treesort_child_not_in_collection(const void *v1, const void *v2)
485{
486 const tTreeSort *x1 = static_cast<const tTreeSort *>(v1);
487 const tTreeSort *x2 = static_cast<const tTreeSort *>(v2);
488
489 /* Among objects first come the ones in the collection, followed by the ones not on it.
490 * This way we can have the dashed lines in a separate style connecting the former. */
492 return (x1->te->flag & TE_CHILD_NOT_IN_COLLECTION) ? 1 : -1;
493 }
494 return 0;
495}
496
497/* alphabetical comparator */
498static int treesort_alpha(const void *v1, const void *v2)
499{
500 const tTreeSort *x1 = static_cast<const tTreeSort *>(v1);
501 const tTreeSort *x2 = static_cast<const tTreeSort *>(v2);
502
503 int comp = BLI_strcasecmp_natural(x1->name, x2->name);
504
505 if (comp > 0) {
506 return 1;
507 }
508 if (comp < 0) {
509 return -1;
510 }
511 return 0;
512}
513
514/* this is nice option for later? doesn't look too useful... */
515#if 0
516static int treesort_obtype_alpha(const void *v1, const void *v2)
517{
518 const tTreeSort *x1 = v1, *x2 = v2;
519
520 /* first put objects last (hierarchy) */
521 if (x1->idcode == ID_OB && x2->idcode != ID_OB) {
522 return 1;
523 }
524 else if (x2->idcode == ID_OB && x1->idcode != ID_OB) {
525 return -1;
526 }
527 else {
528 /* 2nd we check ob type */
529 if (x1->idcode == ID_OB && x2->idcode == ID_OB) {
530 if (((Object *)x1->id)->type > ((Object *)x2->id)->type) {
531 return 1;
532 }
533 else if (((Object *)x1->id)->type > ((Object *)x2->id)->type) {
534 return -1;
535 }
536 else {
537 return 0;
538 }
539 }
540 else {
541 int comp = BLI_strcasecmp_natural(x1->name, x2->name);
542
543 if (comp > 0) {
544 return 1;
545 }
546 else if (comp < 0) {
547 return -1;
548 }
549 return 0;
550 }
551 }
552}
553#endif
554
555/* sort happens on each subtree individual */
556static void outliner_sort(ListBase *lb)
557{
558 TreeElement *last_te = static_cast<TreeElement *>(lb->last);
559 if (last_te == nullptr) {
560 return;
561 }
562 TreeStoreElem *last_tselem = TREESTORE(last_te);
563
564 /* Sorting rules; only object lists, ID lists, or deform-groups. */
565 if (ELEM(last_tselem->type, TSE_DEFGROUP, TSE_ID_BASE) ||
566 ((last_tselem->type == TSE_SOME_ID) && (last_te->idcode == ID_OB)))
567 {
568 int totelem = BLI_listbase_count(lb);
569
570 if (totelem > 1) {
571 tTreeSort *tear = static_cast<tTreeSort *>(
572 MEM_mallocN(totelem * sizeof(tTreeSort), "tree sort array"));
573 tTreeSort *tp = tear;
574 int skip = 0;
575
576 LISTBASE_FOREACH (TreeElement *, te, lb) {
577 TreeStoreElem *tselem = TREESTORE(te);
578 tp->te = te;
579 tp->name = te->name;
580 tp->idcode = te->idcode;
581
582 if (!ELEM(tselem->type, TSE_SOME_ID, TSE_DEFGROUP)) {
583 tp->idcode = 0; /* Don't sort this. */
584 }
585 if (tselem->type == TSE_ID_BASE) {
586 tp->idcode = 1; /* Do sort this. */
587 }
588
589 tp->id = tselem->id;
590 tp++;
591 }
592
593 /* just sort alphabetically */
594 if (tear->idcode == 1) {
595 qsort(tear, totelem, sizeof(tTreeSort), treesort_alpha);
596 }
597 else {
598 /* keep beginning of list */
599 for (tp = tear, skip = 0; skip < totelem; skip++, tp++) {
600 if (tp->idcode) {
601 break;
602 }
603 }
604
605 if (skip < totelem) {
606 qsort(tear + skip, totelem - skip, sizeof(tTreeSort), treesort_alpha_ob);
607 }
608 }
609
611 tp = tear;
612 while (totelem--) {
613 BLI_addtail(lb, tp->te);
614 tp++;
615 }
616 MEM_freeN(tear);
617 }
618 }
619
620 LISTBASE_FOREACH (TreeElement *, te_iter, lb) {
621 outliner_sort(&te_iter->subtree);
622 }
623}
624
626{
627 TreeElement *last_te = static_cast<TreeElement *>(lb->last);
628 if (last_te == nullptr) {
629 return;
630 }
631 TreeStoreElem *last_tselem = TREESTORE(last_te);
632
633 /* Sorting rules: only object lists. */
634 if ((last_tselem->type == TSE_SOME_ID) && (last_te->idcode == ID_OB)) {
635 int totelem = BLI_listbase_count(lb);
636
637 if (totelem > 1) {
638 tTreeSort *tear = static_cast<tTreeSort *>(
639 MEM_mallocN(totelem * sizeof(tTreeSort), "tree sort array"));
640 tTreeSort *tp = tear;
641
642 LISTBASE_FOREACH (TreeElement *, te, lb) {
643 TreeStoreElem *tselem = TREESTORE(te);
644 tp->te = te;
645 tp->name = te->name;
646 tp->idcode = te->idcode;
647 tp->id = tselem->id;
648 tp++;
649 }
650
651 qsort(tear, totelem, sizeof(tTreeSort), treesort_child_not_in_collection);
652
654 tp = tear;
655 while (totelem--) {
656 BLI_addtail(lb, tp->te);
657 tp++;
658 }
659 MEM_freeN(tear);
660 }
661 }
662
663 LISTBASE_FOREACH (TreeElement *, te_iter, lb) {
664 outliner_collections_children_sort(&te_iter->subtree);
665 }
666}
667
670/* -------------------------------------------------------------------- */
678
684 ARegion *region,
686{
687 View2D *v2d = &region->v2d;
688
689 if (focus->tselem != nullptr) {
690 outliner_set_coordinates(region, space_outliner);
691
692 TreeElement *te_new = outliner_find_tree_element(&space_outliner->tree, focus->tselem);
693
694 if (te_new != nullptr) {
695 int ys_new = te_new->ys;
696 int ys_old = focus->ys;
697
698 float y_move = std::min(float(ys_new - ys_old), -v2d->cur.ymax);
699 BLI_rctf_translate(&v2d->cur, 0, y_move);
700 }
701 else {
702 return;
703 }
704 }
705}
706
711
713{
714 TreeStoreElem *tselem = TREESTORE(te);
715 return ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB));
716}
717
722 const SpaceOutliner *space_outliner,
723 TreeElement *te,
724 const float limit,
725 bool (*callback_test)(TreeElement *))
726{
727 if (callback_test(te)) {
728 return te;
729 }
730
731 if (TSELEM_OPEN(te->store_elem, space_outliner)) {
732 LISTBASE_FOREACH (TreeElement *, te_iter, &te->subtree) {
734 space_outliner, te_iter, limit, callback_test);
735 if (te_sub != nullptr) {
736 return te_sub;
737 }
738 }
739 }
740
741 return nullptr;
742}
743
755 const float view_co,
756 const float view_co_limit)
757{
758 TreeElement *te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_co);
759
760 bool (*callback_test)(TreeElement *);
761 if ((space_outliner->outlinevis == SO_VIEW_LAYER) &&
762 (space_outliner->filter & SO_FILTER_NO_COLLECTION))
763 {
764 callback_test = test_object_callback;
765 }
766 else {
767 callback_test = test_collection_callback;
768 }
769
770 while (te != nullptr) {
772 space_outliner, te, view_co_limit, callback_test);
773 if (te_sub != nullptr) {
774 /* Skip the element if it was not visible to start with. */
775 if (te->ys + UI_UNIT_Y > view_co_limit) {
776 return te_sub;
777 }
778 return nullptr;
779 }
780
781 if (te->next) {
782 te = te->next;
783 continue;
784 }
785
786 if (te->parent == nullptr) {
787 break;
788 }
789
790 while (te->parent) {
791 if (te->parent->next) {
792 te = te->parent->next;
793 break;
794 }
795 te = te->parent;
796 }
797 }
798
799 return nullptr;
800}
801
810 ARegion *region,
812{
813 float limit = region->v2d.cur.ymin;
814
815 outliner_set_coordinates(region, space_outliner);
816
818 space_outliner, region->v2d.cur.ymax, limit);
819
820 if (te != nullptr) {
821 focus->tselem = TREESTORE(te);
822 focus->ys = te->ys;
823 }
824 else {
825 focus->tselem = nullptr;
826 }
827}
828
829static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner)
830{
831 int exclude_filter = space_outliner->filter & ~SO_FILTER_OB_STATE;
832
833 if (space_outliner->search_string[0] != 0) {
834 exclude_filter |= SO_FILTER_SEARCH;
835 }
836 else {
837 exclude_filter &= ~SO_FILTER_SEARCH;
838 }
839
840 /* Let's have this for the collection options at first. */
841 if (!SUPPORT_FILTER_OUTLINER(space_outliner)) {
842 return (exclude_filter & SO_FILTER_SEARCH);
843 }
844
845 if (space_outliner->filter & SO_FILTER_NO_OBJECT) {
846 exclude_filter |= SO_FILTER_OB_TYPE;
847 }
848
849 switch (space_outliner->filter_state) {
851 exclude_filter |= SO_FILTER_OB_STATE_VISIBLE;
852 break;
854 exclude_filter |= SO_FILTER_OB_STATE_SELECTED;
855 break;
857 exclude_filter |= SO_FILTER_OB_STATE_ACTIVE;
858 break;
860 exclude_filter |= SO_FILTER_OB_STATE_SELECTABLE;
861 break;
862 }
863
864 return exclude_filter;
865}
866
867static bool outliner_element_visible_get(const Scene *scene,
868 ViewLayer *view_layer,
869 TreeElement *te,
870 const int exclude_filter)
871{
872 if ((exclude_filter & SO_FILTER_ANY) == 0) {
873 return true;
874 }
875
876 TreeStoreElem *tselem = TREESTORE(te);
877 if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) {
878 if ((exclude_filter & SO_FILTER_OB_TYPE) == SO_FILTER_OB_TYPE) {
879 return false;
880 }
881
882 Object *ob = (Object *)tselem->id;
883 Base *base = (Base *)te->directdata;
884 BLI_assert((base == nullptr) || (base->object == ob));
885
886 if (exclude_filter & SO_FILTER_OB_TYPE) {
887 switch (ob->type) {
888 case OB_MESH:
889 if (exclude_filter & SO_FILTER_NO_OB_MESH) {
890 return false;
891 }
892 break;
893 case OB_ARMATURE:
894 if (exclude_filter & SO_FILTER_NO_OB_ARMATURE) {
895 return false;
896 }
897 break;
898 case OB_EMPTY:
899 if (exclude_filter & SO_FILTER_NO_OB_EMPTY) {
900 return false;
901 }
902 break;
903 case OB_LAMP:
904 if (exclude_filter & SO_FILTER_NO_OB_LAMP) {
905 return false;
906 }
907 break;
908 case OB_CAMERA:
909 if (exclude_filter & SO_FILTER_NO_OB_CAMERA) {
910 return false;
911 }
912 break;
913 case OB_GREASE_PENCIL:
914 if (exclude_filter & SO_FILTER_NO_OB_GREASE_PENCIL) {
915 return false;
916 }
917 break;
918 default:
919 if (exclude_filter & SO_FILTER_NO_OB_OTHERS) {
920 return false;
921 }
922 break;
923 }
924 }
925
926 if (exclude_filter & SO_FILTER_OB_STATE) {
927 if (base == nullptr) {
928 BKE_view_layer_synced_ensure(scene, view_layer);
929 base = BKE_view_layer_base_find(view_layer, ob);
930
931 if (base == nullptr) {
932 return false;
933 }
934 }
935
936 bool is_visible = true;
937 if (exclude_filter & SO_FILTER_OB_STATE_VISIBLE) {
939 is_visible = false;
940 }
941 }
942 else if (exclude_filter & SO_FILTER_OB_STATE_SELECTED) {
943 if ((base->flag & BASE_SELECTED) == 0) {
944 is_visible = false;
945 }
946 }
947 else if (exclude_filter & SO_FILTER_OB_STATE_SELECTABLE) {
948 if ((base->flag & BASE_SELECTABLE) == 0) {
949 is_visible = false;
950 }
951 }
952 else {
953 BLI_assert(exclude_filter & SO_FILTER_OB_STATE_ACTIVE);
954 BKE_view_layer_synced_ensure(scene, view_layer);
955 if (base != BKE_view_layer_active_base_get(view_layer)) {
956 is_visible = false;
957 }
958 }
959
960 if (exclude_filter & SO_FILTER_OB_STATE_INVERSE) {
961 is_visible = !is_visible;
962 }
963
964 return is_visible;
965 }
966
967 if ((te->parent != nullptr) && (TREESTORE(te->parent)->type == TSE_SOME_ID) &&
968 (te->parent->idcode == ID_OB))
969 {
970 if (exclude_filter & SO_FILTER_NO_CHILDREN) {
971 return false;
972 }
973 }
974 }
975 else if ((te->parent != nullptr) && (TREESTORE(te->parent)->type == TSE_SOME_ID) &&
976 (te->parent->idcode == ID_OB))
977 {
978 if (exclude_filter & SO_FILTER_NO_OB_CONTENT) {
979 return false;
980 }
981 }
982
983 return true;
984}
985
986static bool outliner_filter_has_name(TreeElement *te, const char *name, int flags)
987{
988 /* Use `fnmatch` for shell-style globing.
989 * - Case-insensitive (optionally).
990 * - Don't handle escape characters as "special" characters are not expected in names.
991 * Unlike shell input - `\` should be treated like any other character.
992 */
993 int fn_flag = FNM_NOESCAPE;
994
995 if ((flags & SO_FIND_CASE_SENSITIVE) == 0) {
996 fn_flag |= FNM_CASEFOLD;
997 }
998
999 return fnmatch(name, te->name, fn_flag) == 0;
1000}
1001
1003{
1004 TreeStoreElem *tselem = TREESTORE(te);
1005
1006 if ((tselem->type == TSE_SOME_ID) && (te->idcode == ID_OB)) {
1007 return true;
1008 }
1009
1010 /* Collection instance datablocks should not be extracted. */
1011 if (outliner_is_collection_tree_element(te) && !(te->parent && te->parent->idcode == ID_OB)) {
1012 return true;
1013 }
1014
1015 return false;
1016}
1017
1019 ListBase *parent_subtree)
1020{
1021 TreeElement *te_next = element->next;
1022
1024 TreeElement *te_prev = nullptr;
1025 for (TreeElement *te = static_cast<TreeElement *>(element->subtree.last); te; te = te_prev) {
1026 te_prev = te->prev;
1027
1029 continue;
1030 }
1031
1032 te_next = te;
1033 BLI_remlink(&element->subtree, te);
1034 BLI_insertlinkafter(parent_subtree, element->prev, te);
1035 te->parent = element->parent;
1036 }
1037 }
1038
1039 outliner_free_tree_element(element, parent_subtree);
1040 return te_next;
1041}
1042
1043static int outliner_filter_subtree(SpaceOutliner *space_outliner,
1044 const Scene *scene,
1045 ViewLayer *view_layer,
1046 ListBase *lb,
1047 const char *search_string,
1048 const int exclude_filter)
1049{
1050 TreeElement *te, *te_next;
1051 TreeStoreElem *tselem;
1052
1053 for (te = static_cast<TreeElement *>(lb->first); te; te = te_next) {
1054 te_next = te->next;
1055 if (outliner_element_visible_get(scene, view_layer, te, exclude_filter) == false) {
1056 /* Don't free the tree, but extract the children from the parent and add to this tree. */
1057 /* This also needs filtering the subtree prior (see #69246). */
1059 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter);
1061 continue;
1062 }
1063 if ((exclude_filter & SO_FILTER_SEARCH) == 0) {
1064 /* Filter subtree too. */
1066 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter);
1067 continue;
1068 }
1069
1070 if (!outliner_filter_has_name(te, search_string, space_outliner->search_flags)) {
1071 /* item isn't something we're looking for, but...
1072 * - if the subtree is expanded, check if there are any matches that can be easily found
1073 * so that searching for "cu" in the default scene will still match the Cube
1074 * - otherwise, we can't see within the subtree and the item doesn't match,
1075 * so these can be safely ignored (i.e. the subtree can get freed)
1076 */
1077 tselem = TREESTORE(te);
1078
1079 /* flag as not a found item */
1080 tselem->flag &= ~TSE_SEARCHMATCH;
1081
1082 if (!TSELEM_OPEN(tselem, space_outliner) ||
1084 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter) == 0)
1085 {
1087 }
1088 }
1089 else {
1090 tselem = TREESTORE(te);
1091
1092 /* flag as a found item - we can then highlight it */
1093 tselem->flag |= TSE_SEARCHMATCH;
1094
1095 /* filter subtree too */
1097 space_outliner, scene, view_layer, &te->subtree, search_string, exclude_filter);
1098 }
1099 }
1100
1101 /* if there are still items in the list, that means that there were still some matches */
1102 return (BLI_listbase_is_empty(lb) == false);
1103}
1104
1105static void outliner_filter_tree(SpaceOutliner *space_outliner,
1106 const Scene *scene,
1107 ViewLayer *view_layer)
1108{
1109 char search_buff[sizeof(SpaceOutliner::search_string) + 2];
1110 char *search_string;
1111
1112 const int exclude_filter = outliner_exclude_filter_get(space_outliner);
1113
1114 if (exclude_filter == 0) {
1115 return;
1116 }
1117
1118 if (space_outliner->search_flags & SO_FIND_COMPLETE) {
1119 search_string = space_outliner->search_string;
1120 }
1121 else {
1122 /* Implicitly add heading/trailing wildcards if needed. */
1123 BLI_strncpy_ensure_pad(search_buff, space_outliner->search_string, '*', sizeof(search_buff));
1124 search_string = search_buff;
1125 }
1126
1128 space_outliner, scene, view_layer, &space_outliner->tree, search_string, exclude_filter);
1129}
1130
1132{
1133 ID *id_iter;
1134 FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
1135 id_iter->newid = nullptr;
1136 }
1138}
1139
1142/* -------------------------------------------------------------------- */
1147 Scene *scene,
1148 ViewLayer *view_layer,
1149 SpaceOutliner *space_outliner,
1150 ARegion *region)
1151{
1152 /* Are we looking for something - we want to tag parents to filter child matches
1153 * - NOT in data-blocks view - searching all data-blocks takes way too long to be useful
1154 * - this variable is only set once per tree build */
1155 if (space_outliner->search_string[0] != 0 && space_outliner->outlinevis != SO_DATA_API) {
1156 space_outliner->search_flags |= SO_SEARCH_RECURSIVE;
1157 }
1158 else {
1159 space_outliner->search_flags &= ~SO_SEARCH_RECURSIVE;
1160 }
1161
1162 if (space_outliner->runtime->tree_hash && (space_outliner->storeflag & SO_TREESTORE_REBUILD) &&
1163 space_outliner->treestore)
1164 {
1165 space_outliner->runtime->tree_hash->rebuild_from_treestore(*space_outliner->treestore);
1166 }
1167 space_outliner->storeflag &= ~SO_TREESTORE_REBUILD;
1168
1169 if (region->do_draw & RGN_DRAW_NO_REBUILD) {
1170 BLI_assert_msg(space_outliner->runtime->tree_display != nullptr,
1171 "Skipping rebuild before tree was built properly, a full redraw should be "
1172 "triggered instead");
1173 return;
1174 }
1175
1176 /* Enable for benchmarking. Starts a timer, results will be printed on function exit. */
1177 // SCOPED_TIMER("Outliner Rebuild");
1178 // SCOPED_TIMER_AVERAGED("Outliner Rebuild");
1179
1181 outliner_store_scrolling_position(space_outliner, region, &focus);
1182
1183 outliner_free_tree(&space_outliner->tree);
1184 outliner_storage_cleanup(space_outliner);
1185
1187 space_outliner->outlinevis, *space_outliner);
1188
1189 /* All tree displays should be created as sub-classes of AbstractTreeDisplay. */
1190 BLI_assert(space_outliner->runtime->tree_display != nullptr);
1191
1192 TreeSourceData source_data{*mainvar, *scene, *view_layer};
1193 space_outliner->tree = space_outliner->runtime->tree_display->build_tree(source_data);
1194
1195 if ((space_outliner->flag & SO_SKIP_SORT_ALPHA) == 0) {
1196 outliner_sort(&space_outliner->tree);
1197 }
1198 else if ((space_outliner->filter & SO_FILTER_NO_CHILDREN) == 0) {
1199 /* We group the children that are in the collection before the ones that are not.
1200 * This way we can try to draw them in a different style altogether.
1201 * We also have to respect the original order of the elements in case alphabetical
1202 * sorting is not enabled. This keep object data and modifiers before its children. */
1203 outliner_collections_children_sort(&space_outliner->tree);
1204 }
1205
1206 outliner_filter_tree(space_outliner, scene, view_layer);
1207 outliner_restore_scrolling_position(space_outliner, region, &focus);
1208
1209 /* `ID.newid` pointer is abused when building tree, DO NOT call #BKE_main_id_newptr_and_tag_clear
1210 * as this expects valid IDs in this pointer, not random unknown data. */
1212}
1213
1216} // 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:500
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:494
#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 BLI_INLINE
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
#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_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:130
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
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 * 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)
@ BLI_MEMPOOL_ALLOW_ITER
Definition BLI_mempool.h:95
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
void BLI_rctf_translate(struct rctf *rect, float x, float y)
Definition rct.c:567
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:1003
@ ID_OB
Object groups, one object can be in many groups at once.
@ BASE_ENABLED_AND_VISIBLE_IN_DEFAULT_VIEWPORT
@ OB_EMPTY
@ OB_CAMERA
@ OB_GREASE_PENCIL
@ OB_ARMATURE
@ OB_LAMP
@ OB_MESH
#define TSE_IS_REAL_ID(_tse)
@ TSE_BONE_COLLECTION
@ TSE_POSE_CHANNEL
@ TSE_CONSTRAINT_BASE
@ TSE_LIBRARY_OVERRIDE_OPERATION
@ TSE_MODIFIER_BASE
@ TSE_GP_LAYER
@ TSE_SEQUENCE_DUP
@ TSE_RNA_ARRAY_ELEM
@ TSE_SEQUENCE
@ TSE_GPENCIL_EFFECT
@ TSE_LINKED_NODE_TREE
@ 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_SEQ_STRIP
@ 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
Read Guarded memory(de)allocation.
#define UI_UNIT_Y
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)
FreestyleLineStyle linestyle
GPU_SHADER_INTERFACE_INFO(depth_2d_update_iface, "").smooth(Type fragColor push_constant(Type::VEC2, "extent") .push_constant(Type source_data
KDTree_3d * tree
#define GS(x)
Definition iris.cc:202
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
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_build_tree(Main *mainvar, Scene *scene, ViewLayer *view_layer, SpaceOutliner *space_outliner, ARegion *region)
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)
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)
short flag
struct Object * object
struct FreestyleLineStyle * linestyle
struct FreestyleLineSet * next
Definition DNA_ID.h:413
int tag
Definition DNA_ID.h:434
struct ID * newid
Definition DNA_ID.h:417
char name[66]
Definition DNA_ID.h:425
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
Establish and manage Outliner trees for different display modes.