Compare commits
15 Commits
refactor-i
...
outliner-c
Author | SHA1 | Date | |
---|---|---|---|
ca7079a44e | |||
fdd9cb713e | |||
8c4f121492 | |||
c796364f05 | |||
4a572f06e9 | |||
fa91f90287 | |||
2d60f64960 | |||
fc6338d863 | |||
d962ca7387 | |||
e1a361d287 | |||
8b787089b3 | |||
126b6f8cc7 | |||
86fe1ec968 | |||
6f87489536 | |||
cf94571762 |
@@ -1640,6 +1640,7 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
|
|||||||
}
|
}
|
||||||
space_outliner->treehash = NULL;
|
space_outliner->treehash = NULL;
|
||||||
space_outliner->tree.first = space_outliner->tree.last = NULL;
|
space_outliner->tree.first = space_outliner->tree.last = NULL;
|
||||||
|
space_outliner->runtime = NULL;
|
||||||
}
|
}
|
||||||
else if (sl->spacetype == SPACE_IMAGE) {
|
else if (sl->spacetype == SPACE_IMAGE) {
|
||||||
SpaceImage *sima = (SpaceImage *)sl;
|
SpaceImage *sima = (SpaceImage *)sl;
|
||||||
|
@@ -56,7 +56,8 @@ template<typename T> class ListBaseWrapper {
|
|||||||
|
|
||||||
Iterator &operator++()
|
Iterator &operator++()
|
||||||
{
|
{
|
||||||
current_ = current_->next;
|
/* Some types store next/prev using `void *`, so cast is necessary. */
|
||||||
|
current_ = static_cast<T *>(current_->next);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,8 +44,14 @@ set(SRC
|
|||||||
outliner_tree.c
|
outliner_tree.c
|
||||||
outliner_utils.c
|
outliner_utils.c
|
||||||
space_outliner.c
|
space_outliner.c
|
||||||
|
tree/common.cc
|
||||||
|
tree/tree_display.cc
|
||||||
|
tree/tree_display_libraries.cc
|
||||||
|
tree/tree_display_view_layer.cc
|
||||||
|
|
||||||
outliner_intern.h
|
outliner_intern.h
|
||||||
|
tree/tree_display.h
|
||||||
|
tree/tree_display.hh
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB
|
set(LIB
|
||||||
|
@@ -25,6 +25,10 @@
|
|||||||
|
|
||||||
#include "RNA_types.h"
|
#include "RNA_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/* internal exports only */
|
/* internal exports only */
|
||||||
|
|
||||||
struct ARegion;
|
struct ARegion;
|
||||||
@@ -42,6 +46,13 @@ struct bPoseChannel;
|
|||||||
struct wmKeyConfig;
|
struct wmKeyConfig;
|
||||||
struct wmOperatorType;
|
struct wmOperatorType;
|
||||||
|
|
||||||
|
typedef struct SpaceOutliner_Runtime {
|
||||||
|
/**
|
||||||
|
* Internal C++ object to create and manage the tree for a specific display type (View Layers,
|
||||||
|
* Scenes, Blender File, etc.). */
|
||||||
|
struct TreeDisplay *tree_display;
|
||||||
|
} SpaceOutliner_Runtime;
|
||||||
|
|
||||||
typedef enum TreeElementInsertType {
|
typedef enum TreeElementInsertType {
|
||||||
TE_INSERT_BEFORE,
|
TE_INSERT_BEFORE,
|
||||||
TE_INSERT_AFTER,
|
TE_INSERT_AFTER,
|
||||||
@@ -534,3 +545,7 @@ void outliner_tag_redraw_avoid_rebuild_on_open_change(const struct SpaceOutliner
|
|||||||
/* outliner_sync.c ---------------------------------------------- */
|
/* outliner_sync.c ---------------------------------------------- */
|
||||||
|
|
||||||
void outliner_sync_selection(const struct bContext *C, struct SpaceOutliner *space_outliner);
|
void outliner_sync_selection(const struct bContext *C, struct SpaceOutliner *space_outliner);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
@@ -85,6 +85,7 @@
|
|||||||
#include "UI_interface.h"
|
#include "UI_interface.h"
|
||||||
|
|
||||||
#include "outliner_intern.h"
|
#include "outliner_intern.h"
|
||||||
|
#include "tree/tree_display.h"
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
# include "BLI_math_base.h" /* M_PI */
|
# include "BLI_math_base.h" /* M_PI */
|
||||||
@@ -94,7 +95,6 @@
|
|||||||
static TreeElement *outliner_add_collection_recursive(SpaceOutliner *space_outliner,
|
static TreeElement *outliner_add_collection_recursive(SpaceOutliner *space_outliner,
|
||||||
Collection *collection,
|
Collection *collection,
|
||||||
TreeElement *ten);
|
TreeElement *ten);
|
||||||
static void outliner_make_object_parent_hierarchy(ListBase *lb);
|
|
||||||
static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner);
|
static int outliner_exclude_filter_get(const SpaceOutliner *space_outliner);
|
||||||
|
|
||||||
/* ********************************************************* */
|
/* ********************************************************* */
|
||||||
@@ -237,14 +237,6 @@ void outliner_free_tree_element(TreeElement *element, ListBase *parent_subtree)
|
|||||||
|
|
||||||
/* ********************************************************* */
|
/* ********************************************************* */
|
||||||
|
|
||||||
/* Prototype, see functions below */
|
|
||||||
static TreeElement *outliner_add_element(SpaceOutliner *space_outliner,
|
|
||||||
ListBase *lb,
|
|
||||||
void *idv,
|
|
||||||
TreeElement *parent,
|
|
||||||
short type,
|
|
||||||
short index);
|
|
||||||
|
|
||||||
/* -------------------------------------------------------- */
|
/* -------------------------------------------------------- */
|
||||||
|
|
||||||
bool outliner_requires_rebuild_on_select_or_active_change(const SpaceOutliner *space_outliner)
|
bool outliner_requires_rebuild_on_select_or_active_change(const SpaceOutliner *space_outliner)
|
||||||
@@ -920,12 +912,12 @@ static void outliner_add_id_contents(SpaceOutliner *space_outliner,
|
|||||||
* \note: If child items are only added to the tree if the item is open, the TSE_ type _must_ be
|
* \note: If child items are only added to the tree if the item is open, the TSE_ type _must_ be
|
||||||
* added to #outliner_element_needs_rebuild_on_open_change().
|
* added to #outliner_element_needs_rebuild_on_open_change().
|
||||||
*/
|
*/
|
||||||
static TreeElement *outliner_add_element(SpaceOutliner *space_outliner,
|
TreeElement *outliner_add_element(SpaceOutliner *space_outliner,
|
||||||
ListBase *lb,
|
ListBase *lb,
|
||||||
void *idv,
|
void *idv,
|
||||||
TreeElement *parent,
|
TreeElement *parent,
|
||||||
short type,
|
short type,
|
||||||
short index)
|
short index)
|
||||||
{
|
{
|
||||||
TreeElement *te;
|
TreeElement *te;
|
||||||
TreeStoreElem *tselem;
|
TreeStoreElem *tselem;
|
||||||
@@ -1390,110 +1382,6 @@ static void outliner_add_seq_dup(SpaceOutliner *space_outliner,
|
|||||||
|
|
||||||
/* ----------------------------------------------- */
|
/* ----------------------------------------------- */
|
||||||
|
|
||||||
static const char *outliner_idcode_to_plural(short idcode)
|
|
||||||
{
|
|
||||||
const char *propname = BKE_idtype_idcode_to_name_plural(idcode);
|
|
||||||
PropertyRNA *prop = RNA_struct_type_find_property(&RNA_BlendData, propname);
|
|
||||||
return (prop) ? RNA_property_ui_name(prop) : "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool outliner_library_id_show(Library *lib, ID *id, short filter_id_type)
|
|
||||||
{
|
|
||||||
if (id->lib != lib) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter_id_type == ID_GR) {
|
|
||||||
/* Don't show child collections of non-scene master collection,
|
|
||||||
* they are already shown as children. */
|
|
||||||
Collection *collection = (Collection *)id;
|
|
||||||
bool has_non_scene_parent = false;
|
|
||||||
|
|
||||||
LISTBASE_FOREACH (CollectionParent *, cparent, &collection->parents) {
|
|
||||||
if (!(cparent->collection->flag & COLLECTION_IS_MASTER)) {
|
|
||||||
has_non_scene_parent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_non_scene_parent) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TreeElement *outliner_add_library_contents(Main *mainvar,
|
|
||||||
SpaceOutliner *space_outliner,
|
|
||||||
ListBase *lb,
|
|
||||||
Library *lib)
|
|
||||||
{
|
|
||||||
TreeElement *ten, *tenlib = NULL;
|
|
||||||
ListBase *lbarray[MAX_LIBARRAY];
|
|
||||||
int a, tot;
|
|
||||||
short filter_id_type = (space_outliner->filter & SO_FILTER_ID_TYPE) ?
|
|
||||||
space_outliner->filter_id_type :
|
|
||||||
0;
|
|
||||||
|
|
||||||
if (filter_id_type) {
|
|
||||||
lbarray[0] = which_libbase(mainvar, space_outliner->filter_id_type);
|
|
||||||
tot = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tot = set_listbasepointers(mainvar, lbarray);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (a = 0; a < tot; a++) {
|
|
||||||
if (lbarray[a] && lbarray[a]->first) {
|
|
||||||
ID *id = lbarray[a]->first;
|
|
||||||
const bool is_library = (GS(id->name) == ID_LI) && (lib != NULL);
|
|
||||||
|
|
||||||
/* check if there's data in current lib */
|
|
||||||
for (; id; id = id->next) {
|
|
||||||
if (id->lib == lib) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We always want to create an entry for libraries, even if/when we have no more IDs from
|
|
||||||
* them. This invalid state is important to show to user as well.*/
|
|
||||||
if (id != NULL || is_library) {
|
|
||||||
if (!tenlib) {
|
|
||||||
/* Create library tree element on demand, depending if there are any data-blocks. */
|
|
||||||
if (lib) {
|
|
||||||
tenlib = outliner_add_element(space_outliner, lb, lib, NULL, 0, 0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tenlib = outliner_add_element(space_outliner, lb, mainvar, NULL, TSE_ID_BASE, 0);
|
|
||||||
tenlib->name = IFACE_("Current File");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create data-block list parent element on demand. */
|
|
||||||
if (id != NULL) {
|
|
||||||
if (filter_id_type) {
|
|
||||||
ten = tenlib;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ten = outliner_add_element(
|
|
||||||
space_outliner, &tenlib->subtree, lbarray[a], NULL, TSE_ID_BASE, 0);
|
|
||||||
ten->directdata = lbarray[a];
|
|
||||||
ten->name = outliner_idcode_to_plural(GS(id->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (id = lbarray[a]->first; id; id = id->next) {
|
|
||||||
if (outliner_library_id_show(lib, id, filter_id_type)) {
|
|
||||||
outliner_add_element(space_outliner, &ten->subtree, id, ten, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tenlib;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void outliner_add_orphaned_datablocks(Main *mainvar, SpaceOutliner *space_outliner)
|
static void outliner_add_orphaned_datablocks(Main *mainvar, SpaceOutliner *space_outliner)
|
||||||
{
|
{
|
||||||
TreeElement *ten;
|
TreeElement *ten;
|
||||||
@@ -1546,82 +1434,6 @@ static void outliner_add_orphaned_datablocks(Main *mainvar, SpaceOutliner *space
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void outliner_add_layer_collection_objects(SpaceOutliner *space_outliner,
|
|
||||||
ListBase *tree,
|
|
||||||
ViewLayer *layer,
|
|
||||||
LayerCollection *lc,
|
|
||||||
TreeElement *ten)
|
|
||||||
{
|
|
||||||
LISTBASE_FOREACH (CollectionObject *, cob, &lc->collection->gobject) {
|
|
||||||
Base *base = BKE_view_layer_base_find(layer, cob->ob);
|
|
||||||
TreeElement *te_object = outliner_add_element(space_outliner, tree, base->object, ten, 0, 0);
|
|
||||||
te_object->directdata = base;
|
|
||||||
|
|
||||||
if (!(base->flag & BASE_VISIBLE_VIEWLAYER)) {
|
|
||||||
te_object->flag |= TE_DISABLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void outliner_add_layer_collections_recursive(SpaceOutliner *space_outliner,
|
|
||||||
ListBase *tree,
|
|
||||||
ViewLayer *layer,
|
|
||||||
ListBase *layer_collections,
|
|
||||||
TreeElement *parent_ten,
|
|
||||||
const bool show_objects)
|
|
||||||
{
|
|
||||||
LISTBASE_FOREACH (LayerCollection *, lc, layer_collections) {
|
|
||||||
const bool exclude = (lc->flag & LAYER_COLLECTION_EXCLUDE) != 0;
|
|
||||||
TreeElement *ten;
|
|
||||||
|
|
||||||
if (exclude && ((space_outliner->show_restrict_flags & SO_RESTRICT_ENABLE) == 0)) {
|
|
||||||
ten = parent_ten;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ID *id = &lc->collection->id;
|
|
||||||
ten = outliner_add_element(space_outliner, tree, id, parent_ten, TSE_LAYER_COLLECTION, 0);
|
|
||||||
|
|
||||||
ten->name = id->name + 2;
|
|
||||||
ten->directdata = lc;
|
|
||||||
|
|
||||||
/* Open by default, except linked collections, which may contain many elements. */
|
|
||||||
TreeStoreElem *tselem = TREESTORE(ten);
|
|
||||||
if (!(tselem->used || ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY(id))) {
|
|
||||||
tselem->flag &= ~TSE_CLOSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclude || (lc->runtime_flag & LAYER_COLLECTION_VISIBLE_VIEW_LAYER) == 0) {
|
|
||||||
ten->flag |= TE_DISABLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outliner_add_layer_collections_recursive(
|
|
||||||
space_outliner, &ten->subtree, layer, &lc->layer_collections, ten, show_objects);
|
|
||||||
if (!exclude && show_objects) {
|
|
||||||
outliner_add_layer_collection_objects(space_outliner, &ten->subtree, layer, lc, ten);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void outliner_add_view_layer(SpaceOutliner *space_outliner,
|
|
||||||
ListBase *tree,
|
|
||||||
TreeElement *parent,
|
|
||||||
ViewLayer *layer,
|
|
||||||
const bool show_objects)
|
|
||||||
{
|
|
||||||
/* First layer collection is for master collection, don't show it. */
|
|
||||||
LayerCollection *lc = layer->layer_collections.first;
|
|
||||||
if (lc == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
outliner_add_layer_collections_recursive(
|
|
||||||
space_outliner, tree, layer, &lc->layer_collections, parent, show_objects);
|
|
||||||
if (show_objects) {
|
|
||||||
outliner_add_layer_collection_objects(space_outliner, tree, layer, lc, parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BLI_INLINE void outliner_add_collection_init(TreeElement *te, Collection *collection)
|
BLI_INLINE void outliner_add_collection_init(TreeElement *te, Collection *collection)
|
||||||
{
|
{
|
||||||
te->name = BKE_collection_ui_name_get(collection);
|
te->name = BKE_collection_ui_name_get(collection);
|
||||||
@@ -1661,7 +1473,7 @@ static TreeElement *outliner_add_collection_recursive(SpaceOutliner *space_outli
|
|||||||
/* Hierarchy --------------------------------------------- */
|
/* Hierarchy --------------------------------------------- */
|
||||||
|
|
||||||
/* make sure elements are correctly nested */
|
/* make sure elements are correctly nested */
|
||||||
static void outliner_make_object_parent_hierarchy(ListBase *lb)
|
void outliner_make_object_parent_hierarchy(ListBase *lb)
|
||||||
{
|
{
|
||||||
TreeElement *te, *ten, *tep;
|
TreeElement *te, *ten, *tep;
|
||||||
TreeStoreElem *tselem;
|
TreeStoreElem *tselem;
|
||||||
@@ -1686,103 +1498,6 @@ static void outliner_make_object_parent_hierarchy(ListBase *lb)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* For all objects in the tree, lookup the parent in this map,
|
|
||||||
* and move or add tree elements as needed.
|
|
||||||
*/
|
|
||||||
static void outliner_make_object_parent_hierarchy_collections(SpaceOutliner *space_outliner,
|
|
||||||
GHash *object_tree_elements_hash)
|
|
||||||
{
|
|
||||||
GHashIterator gh_iter;
|
|
||||||
GHASH_ITER (gh_iter, object_tree_elements_hash) {
|
|
||||||
Object *child = BLI_ghashIterator_getKey(&gh_iter);
|
|
||||||
|
|
||||||
if (child->parent == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ListBase *child_ob_tree_elements = BLI_ghashIterator_getValue(&gh_iter);
|
|
||||||
ListBase *parent_ob_tree_elements = BLI_ghash_lookup(object_tree_elements_hash, child->parent);
|
|
||||||
if (parent_ob_tree_elements == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LISTBASE_FOREACH (LinkData *, link, parent_ob_tree_elements) {
|
|
||||||
TreeElement *parent_ob_tree_element = link->data;
|
|
||||||
TreeElement *parent_ob_collection_tree_element = NULL;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
/* We always want to remove the child from the direct collection its parent is nested under.
|
|
||||||
* This is particularly important when dealing with multi-level nesting (grandchildren). */
|
|
||||||
parent_ob_collection_tree_element = parent_ob_tree_element->parent;
|
|
||||||
while (!ELEM(TREESTORE(parent_ob_collection_tree_element)->type,
|
|
||||||
TSE_VIEW_COLLECTION_BASE,
|
|
||||||
TSE_LAYER_COLLECTION)) {
|
|
||||||
parent_ob_collection_tree_element = parent_ob_collection_tree_element->parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
LISTBASE_FOREACH (LinkData *, link_iter, child_ob_tree_elements) {
|
|
||||||
TreeElement *child_ob_tree_element = link_iter->data;
|
|
||||||
|
|
||||||
if (child_ob_tree_element->parent == parent_ob_collection_tree_element) {
|
|
||||||
/* Move from the collection subtree into the parent object subtree. */
|
|
||||||
BLI_remlink(&parent_ob_collection_tree_element->subtree, child_ob_tree_element);
|
|
||||||
BLI_addtail(&parent_ob_tree_element->subtree, child_ob_tree_element);
|
|
||||||
child_ob_tree_element->parent = parent_ob_tree_element;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
/* We add the child in the tree even if it is not in the collection.
|
|
||||||
* We deliberately clear its sub-tree though, to make it less prominent. */
|
|
||||||
TreeElement *child_ob_tree_element = outliner_add_element(
|
|
||||||
space_outliner, &parent_ob_tree_element->subtree, child, parent_ob_tree_element, 0, 0);
|
|
||||||
outliner_free_tree(&child_ob_tree_element->subtree);
|
|
||||||
child_ob_tree_element->flag |= TE_CHILD_NOT_IN_COLLECTION;
|
|
||||||
BLI_addtail(child_ob_tree_elements, BLI_genericNodeN(child_ob_tree_element));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a map from Object* to a list of TreeElement* matching the object.
|
|
||||||
*/
|
|
||||||
static void outliner_object_tree_elements_lookup_create_recursive(GHash *object_tree_elements_hash,
|
|
||||||
TreeElement *te_parent)
|
|
||||||
{
|
|
||||||
LISTBASE_FOREACH (TreeElement *, te, &te_parent->subtree) {
|
|
||||||
TreeStoreElem *tselem = TREESTORE(te);
|
|
||||||
|
|
||||||
if (tselem->type == TSE_LAYER_COLLECTION) {
|
|
||||||
outliner_object_tree_elements_lookup_create_recursive(object_tree_elements_hash, te);
|
|
||||||
}
|
|
||||||
else if (tselem->type == 0 && te->idcode == ID_OB) {
|
|
||||||
Object *ob = (Object *)tselem->id;
|
|
||||||
ListBase *tree_elements = BLI_ghash_lookup(object_tree_elements_hash, ob);
|
|
||||||
|
|
||||||
if (tree_elements == NULL) {
|
|
||||||
tree_elements = MEM_callocN(sizeof(ListBase), __func__);
|
|
||||||
BLI_ghash_insert(object_tree_elements_hash, ob, tree_elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
BLI_addtail(tree_elements, BLI_genericNodeN(te));
|
|
||||||
outliner_object_tree_elements_lookup_create_recursive(object_tree_elements_hash, te);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void outliner_object_tree_elements_lookup_free(GHash *object_tree_elements_hash)
|
|
||||||
{
|
|
||||||
GHASH_FOREACH_BEGIN (ListBase *, tree_elements, object_tree_elements_hash) {
|
|
||||||
BLI_freelistN(tree_elements);
|
|
||||||
MEM_freeN(tree_elements);
|
|
||||||
}
|
|
||||||
GHASH_FOREACH_END();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sorting ------------------------------------------------------ */
|
/* Sorting ------------------------------------------------------ */
|
||||||
|
|
||||||
typedef struct tTreeSort {
|
typedef struct tTreeSort {
|
||||||
@@ -2499,59 +2214,23 @@ void outliner_build_tree(Main *mainvar,
|
|||||||
|
|
||||||
outliner_free_tree(&space_outliner->tree);
|
outliner_free_tree(&space_outliner->tree);
|
||||||
outliner_storage_cleanup(space_outliner);
|
outliner_storage_cleanup(space_outliner);
|
||||||
|
outliner_tree_display_destroy(&space_outliner->runtime->tree_display);
|
||||||
|
|
||||||
|
space_outliner->runtime->tree_display = outliner_tree_display_create(space_outliner->outlinevis,
|
||||||
|
space_outliner);
|
||||||
|
if (space_outliner->runtime->tree_display) {
|
||||||
|
TreeSourceData source_data = {.bmain = mainvar, .scene = scene, .view_layer = view_layer};
|
||||||
|
space_outliner->tree = outliner_tree_display_build_tree(space_outliner->runtime->tree_display,
|
||||||
|
&source_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space_outliner->runtime->tree_display) {
|
||||||
|
/* Skip if there's a tree-display that's responsible for adding all elements. */
|
||||||
|
}
|
||||||
/* options */
|
/* options */
|
||||||
if (space_outliner->outlinevis == SO_LIBRARIES) {
|
else if (space_outliner->outlinevis == SO_LIBRARIES) {
|
||||||
Library *lib;
|
/* Ported to new tree-display, should be built there already. */
|
||||||
|
BLI_assert(false);
|
||||||
/* current file first - mainvar provides tselem with unique pointer - not used */
|
|
||||||
ten = outliner_add_library_contents(mainvar, space_outliner, &space_outliner->tree, NULL);
|
|
||||||
if (ten) {
|
|
||||||
tselem = TREESTORE(ten);
|
|
||||||
if (!tselem->used) {
|
|
||||||
tselem->flag &= ~TSE_CLOSED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (lib = mainvar->libraries.first; lib; lib = lib->id.next) {
|
|
||||||
ten = outliner_add_library_contents(mainvar, space_outliner, &space_outliner->tree, lib);
|
|
||||||
/* NULL-check matters, due to filtering there may not be a new element. */
|
|
||||||
if (ten) {
|
|
||||||
lib->id.newid = (ID *)ten;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* make hierarchy */
|
|
||||||
ten = space_outliner->tree.first;
|
|
||||||
if (ten != NULL) {
|
|
||||||
ten = ten->next; /* first one is main */
|
|
||||||
while (ten) {
|
|
||||||
TreeElement *nten = ten->next, *par;
|
|
||||||
tselem = TREESTORE(ten);
|
|
||||||
lib = (Library *)tselem->id;
|
|
||||||
if (lib && lib->parent) {
|
|
||||||
par = (TreeElement *)lib->parent->id.newid;
|
|
||||||
if (tselem->id->tag & LIB_TAG_INDIRECT) {
|
|
||||||
/* Only remove from 'first level' if lib is not also directly used. */
|
|
||||||
BLI_remlink(&space_outliner->tree, ten);
|
|
||||||
BLI_addtail(&par->subtree, ten);
|
|
||||||
ten->parent = par;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Else, make a new copy of the libtree for our parent. */
|
|
||||||
TreeElement *dupten = outliner_add_library_contents(
|
|
||||||
mainvar, space_outliner, &par->subtree, lib);
|
|
||||||
if (dupten) {
|
|
||||||
dupten->parent = par;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ten = nten;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* restore newid pointers */
|
|
||||||
for (lib = mainvar->libraries.first; lib; lib = lib->id.next) {
|
|
||||||
lib->id.newid = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (space_outliner->outlinevis == SO_SCENES) {
|
else if (space_outliner->outlinevis == SO_SCENES) {
|
||||||
Scene *sce;
|
Scene *sce;
|
||||||
@@ -2612,38 +2291,8 @@ void outliner_build_tree(Main *mainvar,
|
|||||||
outliner_add_orphaned_datablocks(mainvar, space_outliner);
|
outliner_add_orphaned_datablocks(mainvar, space_outliner);
|
||||||
}
|
}
|
||||||
else if (space_outliner->outlinevis == SO_VIEW_LAYER) {
|
else if (space_outliner->outlinevis == SO_VIEW_LAYER) {
|
||||||
if (space_outliner->filter & SO_FILTER_NO_COLLECTION) {
|
/* Ported to new tree-display, should be built there already. */
|
||||||
/* Show objects in the view layer. */
|
BLI_assert(false);
|
||||||
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
|
|
||||||
TreeElement *te_object = outliner_add_element(
|
|
||||||
space_outliner, &space_outliner->tree, base->object, NULL, 0, 0);
|
|
||||||
te_object->directdata = base;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((space_outliner->filter & SO_FILTER_NO_CHILDREN) == 0) {
|
|
||||||
outliner_make_object_parent_hierarchy(&space_outliner->tree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Show collections in the view layer. */
|
|
||||||
ten = outliner_add_element(
|
|
||||||
space_outliner, &space_outliner->tree, scene, NULL, TSE_VIEW_COLLECTION_BASE, 0);
|
|
||||||
ten->name = IFACE_("Scene Collection");
|
|
||||||
TREESTORE(ten)->flag &= ~TSE_CLOSED;
|
|
||||||
|
|
||||||
bool show_objects = !(space_outliner->filter & SO_FILTER_NO_OBJECT);
|
|
||||||
outliner_add_view_layer(space_outliner, &ten->subtree, ten, view_layer, show_objects);
|
|
||||||
|
|
||||||
if ((space_outliner->filter & SO_FILTER_NO_CHILDREN) == 0) {
|
|
||||||
GHash *object_tree_elements_hash = BLI_ghash_new(
|
|
||||||
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
|
|
||||||
outliner_object_tree_elements_lookup_create_recursive(object_tree_elements_hash, ten);
|
|
||||||
outliner_make_object_parent_hierarchy_collections(space_outliner,
|
|
||||||
object_tree_elements_hash);
|
|
||||||
outliner_object_tree_elements_lookup_free(object_tree_elements_hash);
|
|
||||||
BLI_ghash_free(object_tree_elements_hash, NULL, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((space_outliner->flag & SO_SKIP_SORT_ALPHA) == 0) {
|
if ((space_outliner->flag & SO_SKIP_SORT_ALPHA) == 0) {
|
||||||
|
@@ -351,11 +351,21 @@ static void outliner_free(SpaceLink *sl)
|
|||||||
if (space_outliner->treehash) {
|
if (space_outliner->treehash) {
|
||||||
BKE_outliner_treehash_free(space_outliner->treehash);
|
BKE_outliner_treehash_free(space_outliner->treehash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (space_outliner->runtime) {
|
||||||
|
MEM_freeN(space_outliner->runtime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* spacetype; init callback */
|
/* spacetype; init callback */
|
||||||
static void outliner_init(wmWindowManager *UNUSED(wm), ScrArea *UNUSED(area))
|
static void outliner_init(wmWindowManager *UNUSED(wm), ScrArea *area)
|
||||||
{
|
{
|
||||||
|
SpaceOutliner *space_outliner = area->spacedata.first;
|
||||||
|
|
||||||
|
if (space_outliner->runtime == NULL) {
|
||||||
|
space_outliner->runtime = MEM_callocN(sizeof(*space_outliner->runtime),
|
||||||
|
"SpaceOutliner_Runtime");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SpaceLink *outliner_duplicate(SpaceLink *sl)
|
static SpaceLink *outliner_duplicate(SpaceLink *sl)
|
||||||
@@ -369,6 +379,10 @@ static SpaceLink *outliner_duplicate(SpaceLink *sl)
|
|||||||
|
|
||||||
space_outliner_new->sync_select_dirty = WM_OUTLINER_SYNC_SELECT_FROM_ALL;
|
space_outliner_new->sync_select_dirty = WM_OUTLINER_SYNC_SELECT_FROM_ALL;
|
||||||
|
|
||||||
|
if (space_outliner->runtime) {
|
||||||
|
space_outliner_new->runtime = MEM_dupallocN(space_outliner->runtime);
|
||||||
|
}
|
||||||
|
|
||||||
return (SpaceLink *)space_outliner_new;
|
return (SpaceLink *)space_outliner_new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
source/blender/editors/space_outliner/tree/common.cc
Normal file
41
source/blender/editors/space_outliner/tree/common.cc
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup spoutliner
|
||||||
|
*
|
||||||
|
* Functions and helpers shared between tree-display types or other tree related code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BKE_idtype.h"
|
||||||
|
|
||||||
|
#include "RNA_access.h"
|
||||||
|
|
||||||
|
#include "tree_display.hh"
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/** \name ID Helpers.
|
||||||
|
*
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
const char *outliner_idcode_to_plural(short idcode)
|
||||||
|
{
|
||||||
|
const char *propname = BKE_idtype_idcode_to_name_plural(idcode);
|
||||||
|
PropertyRNA *prop = RNA_struct_type_find_property(&RNA_BlendData, propname);
|
||||||
|
return (prop) ? RNA_property_ui_name(prop) : "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \} */
|
60
source/blender/editors/space_outliner/tree/tree_display.cc
Normal file
60
source/blender/editors/space_outliner/tree/tree_display.cc
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup spoutliner
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BLI_listbase.h"
|
||||||
|
|
||||||
|
#include "DNA_listBase.h"
|
||||||
|
|
||||||
|
#include "tree_display.hh"
|
||||||
|
|
||||||
|
using namespace blender::ed::outliner;
|
||||||
|
|
||||||
|
TreeDisplay *outliner_tree_display_create(eSpaceOutliner_Mode mode, SpaceOutliner *space_outliner)
|
||||||
|
{
|
||||||
|
AbstractTreeDisplay *tree_display = nullptr;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case SO_SCENES:
|
||||||
|
break;
|
||||||
|
case SO_LIBRARIES:
|
||||||
|
tree_display = new TreeDisplayLibraries(*space_outliner);
|
||||||
|
break;
|
||||||
|
case SO_SEQUENCE:
|
||||||
|
case SO_DATA_API:
|
||||||
|
case SO_ID_ORPHANS:
|
||||||
|
break;
|
||||||
|
case SO_VIEW_LAYER:
|
||||||
|
tree_display = new TreeDisplayViewLayer(*space_outliner);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reinterpret_cast<TreeDisplay *>(tree_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void outliner_tree_display_destroy(TreeDisplay **tree_display)
|
||||||
|
{
|
||||||
|
delete reinterpret_cast<AbstractTreeDisplay *>(*tree_display);
|
||||||
|
*tree_display = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListBase outliner_tree_display_build_tree(TreeDisplay *tree_display, TreeSourceData *source_data)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<AbstractTreeDisplay *>(tree_display)->buildTree(*source_data);
|
||||||
|
}
|
64
source/blender/editors/space_outliner/tree/tree_display.h
Normal file
64
source/blender/editors/space_outliner/tree/tree_display.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup spoutliner
|
||||||
|
*
|
||||||
|
* C-API for the Tree-Display types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DNA_space_types.h"
|
||||||
|
|
||||||
|
struct ListBase;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** C alias for an #AbstractTreeDisplay handle. */
|
||||||
|
typedef struct TreeDisplay TreeDisplay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The data to build the tree from.
|
||||||
|
*/
|
||||||
|
typedef struct TreeSourceData {
|
||||||
|
struct Main *bmain;
|
||||||
|
struct Scene *scene;
|
||||||
|
struct ViewLayer *view_layer;
|
||||||
|
} TreeSourceData;
|
||||||
|
|
||||||
|
TreeDisplay *outliner_tree_display_create(eSpaceOutliner_Mode mode, SpaceOutliner *space_outliner);
|
||||||
|
void outliner_tree_display_destroy(TreeDisplay **tree_display);
|
||||||
|
|
||||||
|
ListBase outliner_tree_display_build_tree(TreeDisplay *tree_display, TreeSourceData *source_data);
|
||||||
|
|
||||||
|
/* The following functions are needed to build the tree. They are calls back into C; the way
|
||||||
|
* elements are created should be refactored and ported to C++ with a new design/API too. */
|
||||||
|
struct TreeElement *outliner_add_element(SpaceOutliner *space_outliner,
|
||||||
|
ListBase *lb,
|
||||||
|
void *idv,
|
||||||
|
struct TreeElement *parent,
|
||||||
|
short type,
|
||||||
|
short index);
|
||||||
|
void outliner_make_object_parent_hierarchy(ListBase *lb);
|
||||||
|
|
||||||
|
const char *outliner_idcode_to_plural(short idcode);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
113
source/blender/editors/space_outliner/tree/tree_display.hh
Normal file
113
source/blender/editors/space_outliner/tree/tree_display.hh
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup spoutliner
|
||||||
|
*
|
||||||
|
* \brief Establish and manage Outliner trees for different display modes.
|
||||||
|
*
|
||||||
|
* Each Outliner display mode (e.g View Layer, Scenes, Blender File) is implemented as a
|
||||||
|
* tree-display class with the #AbstractTreeDisplay interface.
|
||||||
|
*
|
||||||
|
* Their main responsibility is building the Outliner tree for a display mode. For that, they
|
||||||
|
* implement the #buildTree() function, which based on Blender data (#TreeSourceData), builds a
|
||||||
|
* custom tree of whatever data it wants to visualize.
|
||||||
|
* Further, they can implement display mode dependent queries and general operations using the
|
||||||
|
* #AbstractTreeDisplay abstraction as common interface.
|
||||||
|
*
|
||||||
|
* Outliners keep the current tree-display object alive until the next full tree rebuild to keep
|
||||||
|
* access to it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tree_display.h"
|
||||||
|
|
||||||
|
struct ListBase;
|
||||||
|
struct Main;
|
||||||
|
struct SpaceOutliner;
|
||||||
|
struct TreeElement;
|
||||||
|
struct TreeSourceData;
|
||||||
|
|
||||||
|
namespace blender::ed::outliner {
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Tree-Display Interface */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Base Class For Tree-Displays
|
||||||
|
*
|
||||||
|
* Abstract base class defining the interface for tree-display variants.
|
||||||
|
*/
|
||||||
|
class AbstractTreeDisplay {
|
||||||
|
public:
|
||||||
|
AbstractTreeDisplay(SpaceOutliner &space_outliner) : space_outliner_(space_outliner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual ~AbstractTreeDisplay() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a tree for this display mode with the Blender context data given in \a source_data and
|
||||||
|
* the view settings in \a space_outliner.
|
||||||
|
*/
|
||||||
|
virtual ListBase buildTree(const TreeSourceData &source_data) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** All derived classes will need a handle to this, so storing it in the base for convenience. */
|
||||||
|
SpaceOutliner &space_outliner_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* View Layer Tree-Display */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Tree-Display for the View Layer display mode.
|
||||||
|
*/
|
||||||
|
class TreeDisplayViewLayer final : public AbstractTreeDisplay {
|
||||||
|
ViewLayer *view_layer_ = nullptr;
|
||||||
|
bool show_objects_ = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TreeDisplayViewLayer(SpaceOutliner &space_outliner);
|
||||||
|
|
||||||
|
ListBase buildTree(const TreeSourceData &source_data) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void add_view_layer(ListBase &, TreeElement &);
|
||||||
|
void add_layer_collections_recursive(ListBase &, ListBase &, TreeElement &);
|
||||||
|
void add_layer_collection_objects(ListBase &, LayerCollection &, TreeElement &);
|
||||||
|
void add_layer_collection_objects_children(TreeElement &);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/* Library Tree-Display */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Tree-Display for the Libraries display mode.
|
||||||
|
*/
|
||||||
|
class TreeDisplayLibraries final : public AbstractTreeDisplay {
|
||||||
|
public:
|
||||||
|
TreeDisplayLibraries(SpaceOutliner &space_outliner);
|
||||||
|
|
||||||
|
ListBase buildTree(const TreeSourceData &source_data) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TreeElement *add_library_contents(Main &, ListBase &, Library *) const;
|
||||||
|
bool library_id_filter_poll(Library *lib, ID *id) const;
|
||||||
|
short id_filter_get() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace blender::ed::outliner
|
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup spoutliner
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BLI_listbase.h"
|
||||||
|
#include "BLI_listbase_wrapper.hh"
|
||||||
|
|
||||||
|
#include "BKE_collection.h"
|
||||||
|
#include "BKE_main.h"
|
||||||
|
|
||||||
|
#include "BLT_translation.h"
|
||||||
|
|
||||||
|
#include "../outliner_intern.h"
|
||||||
|
#include "tree_display.hh"
|
||||||
|
|
||||||
|
namespace blender::ed::outliner {
|
||||||
|
|
||||||
|
/* Convenience/readability. */
|
||||||
|
template<typename T> using List = ListBaseWrapper<T>;
|
||||||
|
|
||||||
|
TreeDisplayLibraries::TreeDisplayLibraries(SpaceOutliner &space_outliner)
|
||||||
|
: AbstractTreeDisplay(space_outliner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ListBase TreeDisplayLibraries::buildTree(const TreeSourceData &source_data)
|
||||||
|
{
|
||||||
|
ListBase tree = {nullptr};
|
||||||
|
|
||||||
|
{
|
||||||
|
/* current file first - mainvar provides tselem with unique pointer - not used */
|
||||||
|
TreeElement *ten = add_library_contents(*source_data.bmain, tree, nullptr);
|
||||||
|
TreeStoreElem *tselem;
|
||||||
|
|
||||||
|
if (ten) {
|
||||||
|
tselem = TREESTORE(ten);
|
||||||
|
if (!tselem->used) {
|
||||||
|
tselem->flag &= ~TSE_CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ID *id : List<ID>(source_data.bmain->libraries)) {
|
||||||
|
Library *lib = reinterpret_cast<Library *>(id);
|
||||||
|
TreeElement *ten = add_library_contents(*source_data.bmain, tree, lib);
|
||||||
|
/* NULL-check matters, due to filtering there may not be a new element. */
|
||||||
|
if (ten) {
|
||||||
|
lib->id.newid = (ID *)ten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make hierarchy */
|
||||||
|
for (TreeElement *ten : List<TreeElement>(tree)) {
|
||||||
|
if (ten == tree.first) {
|
||||||
|
/* First item is main, skip. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeStoreElem *tselem = TREESTORE(ten);
|
||||||
|
Library *lib = (Library *)tselem->id;
|
||||||
|
BLI_assert(!lib || (GS(lib->id.name) == ID_LI));
|
||||||
|
if (!lib || !lib->parent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeElement *parent = (TreeElement *)lib->parent->id.newid;
|
||||||
|
|
||||||
|
if (tselem->id->tag & LIB_TAG_INDIRECT) {
|
||||||
|
/* Only remove from 'first level' if lib is not also directly used. */
|
||||||
|
BLI_remlink(&tree, ten);
|
||||||
|
BLI_addtail(&parent->subtree, ten);
|
||||||
|
ten->parent = parent;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Else, make a new copy of the libtree for our parent. */
|
||||||
|
TreeElement *dupten = add_library_contents(*source_data.bmain, parent->subtree, lib);
|
||||||
|
if (dupten) {
|
||||||
|
dupten->parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* restore newid pointers */
|
||||||
|
for (ID *library_id : List<ID>(source_data.bmain->libraries)) {
|
||||||
|
library_id->newid = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeElement *TreeDisplayLibraries::add_library_contents(Main &mainvar,
|
||||||
|
ListBase &lb,
|
||||||
|
Library *lib) const
|
||||||
|
{
|
||||||
|
const short filter_id_type = id_filter_get();
|
||||||
|
|
||||||
|
ListBase *lbarray[MAX_LIBARRAY];
|
||||||
|
int tot;
|
||||||
|
if (filter_id_type) {
|
||||||
|
lbarray[0] = which_libbase(&mainvar, space_outliner_.filter_id_type);
|
||||||
|
tot = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tot = set_listbasepointers(&mainvar, lbarray);
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeElement *tenlib = nullptr;
|
||||||
|
for (int a = 0; a < tot; a++) {
|
||||||
|
if (!lbarray[a] || !lbarray[a]->first) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID *id = static_cast<ID *>(lbarray[a]->first);
|
||||||
|
const bool is_library = (GS(id->name) == ID_LI) && (lib != nullptr);
|
||||||
|
|
||||||
|
/* check if there's data in current lib */
|
||||||
|
for (ID *id_iter : List<ID>(lbarray[a])) {
|
||||||
|
if (id_iter->lib == lib) {
|
||||||
|
id = id_iter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We always want to create an entry for libraries, even if/when we have no more IDs from
|
||||||
|
* them. This invalid state is important to show to user as well.*/
|
||||||
|
if (id != nullptr || is_library) {
|
||||||
|
if (!tenlib) {
|
||||||
|
/* Create library tree element on demand, depending if there are any data-blocks. */
|
||||||
|
if (lib) {
|
||||||
|
tenlib = outliner_add_element(&space_outliner_, &lb, lib, nullptr, 0, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tenlib = outliner_add_element(&space_outliner_, &lb, &mainvar, nullptr, TSE_ID_BASE, 0);
|
||||||
|
tenlib->name = IFACE_("Current File");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create data-block list parent element on demand. */
|
||||||
|
if (id != nullptr) {
|
||||||
|
TreeElement *ten;
|
||||||
|
|
||||||
|
if (filter_id_type) {
|
||||||
|
ten = tenlib;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ten = outliner_add_element(
|
||||||
|
&space_outliner_, &tenlib->subtree, lbarray[a], nullptr, TSE_ID_BASE, 0);
|
||||||
|
ten->directdata = lbarray[a];
|
||||||
|
ten->name = outliner_idcode_to_plural(GS(id->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ID *id : List<ID>(lbarray[a])) {
|
||||||
|
if (library_id_filter_poll(lib, id)) {
|
||||||
|
outliner_add_element(&space_outliner_, &ten->subtree, id, ten, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tenlib;
|
||||||
|
}
|
||||||
|
|
||||||
|
short TreeDisplayLibraries::id_filter_get() const
|
||||||
|
{
|
||||||
|
if (space_outliner_.filter & SO_FILTER_ID_TYPE) {
|
||||||
|
return space_outliner_.filter_id_type;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TreeDisplayLibraries::library_id_filter_poll(Library *lib, ID *id) const
|
||||||
|
{
|
||||||
|
if (id->lib != lib) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id_filter_get() == ID_GR) {
|
||||||
|
/* Don't show child collections of non-scene master collection,
|
||||||
|
* they are already shown as children. */
|
||||||
|
Collection *collection = (Collection *)id;
|
||||||
|
bool has_non_scene_parent = false;
|
||||||
|
|
||||||
|
for (CollectionParent *cparent : List<CollectionParent>(collection->parents)) {
|
||||||
|
if (!(cparent->collection->flag & COLLECTION_IS_MASTER)) {
|
||||||
|
has_non_scene_parent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_non_scene_parent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::ed::outliner
|
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
* \ingroup spoutliner
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "DNA_scene_types.h"
|
||||||
|
|
||||||
|
#include "BKE_layer.h"
|
||||||
|
|
||||||
|
#include "BLI_listbase.h"
|
||||||
|
#include "BLI_listbase_wrapper.hh"
|
||||||
|
#include "BLI_map.hh"
|
||||||
|
#include "BLI_vector.hh"
|
||||||
|
|
||||||
|
#include "BLT_translation.h"
|
||||||
|
|
||||||
|
#include "../outliner_intern.h"
|
||||||
|
#include "tree_display.hh"
|
||||||
|
|
||||||
|
namespace blender::ed::outliner {
|
||||||
|
|
||||||
|
/* Convenience/readability. */
|
||||||
|
template<typename T> using List = ListBaseWrapper<T>;
|
||||||
|
|
||||||
|
class ObjectsChildrenBuilder {
|
||||||
|
using TreeChildren = Vector<TreeElement *>;
|
||||||
|
using ObjectTreeElementsMap = Map<Object *, TreeChildren>;
|
||||||
|
|
||||||
|
SpaceOutliner &outliner_;
|
||||||
|
ObjectTreeElementsMap object_tree_elements_map_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ObjectsChildrenBuilder(SpaceOutliner &);
|
||||||
|
~ObjectsChildrenBuilder() = default;
|
||||||
|
|
||||||
|
void operator()(TreeElement &collection_tree_elem);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void object_tree_elements_lookup_create_recursive(TreeElement *);
|
||||||
|
void make_object_parent_hierarchy_collections();
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/** \name Tree-Display for a View Layer.
|
||||||
|
*
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
TreeDisplayViewLayer::TreeDisplayViewLayer(SpaceOutliner &space_outliner)
|
||||||
|
: AbstractTreeDisplay(space_outliner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ListBase TreeDisplayViewLayer::buildTree(const TreeSourceData &source_data)
|
||||||
|
{
|
||||||
|
ListBase tree = {nullptr};
|
||||||
|
|
||||||
|
view_layer_ = source_data.view_layer;
|
||||||
|
show_objects_ = !(space_outliner_.filter & SO_FILTER_NO_OBJECT);
|
||||||
|
|
||||||
|
const bool show_children = (space_outliner_.filter & SO_FILTER_NO_CHILDREN) == 0;
|
||||||
|
|
||||||
|
if (space_outliner_.filter & SO_FILTER_NO_COLLECTION) {
|
||||||
|
/* Show objects in the view layer. */
|
||||||
|
for (Base *base : List<Base>(view_layer_->object_bases)) {
|
||||||
|
TreeElement *te_object = outliner_add_element(
|
||||||
|
&space_outliner_, &tree, base->object, nullptr, 0, 0);
|
||||||
|
te_object->directdata = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_children) {
|
||||||
|
outliner_make_object_parent_hierarchy(&tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Show collections in the view layer. */
|
||||||
|
TreeElement &ten = *outliner_add_element(
|
||||||
|
&space_outliner_, &tree, source_data.scene, nullptr, TSE_VIEW_COLLECTION_BASE, 0);
|
||||||
|
ten.name = IFACE_("Scene Collection");
|
||||||
|
TREESTORE(&ten)->flag &= ~TSE_CLOSED;
|
||||||
|
|
||||||
|
add_view_layer(ten.subtree, ten);
|
||||||
|
if (show_children) {
|
||||||
|
add_layer_collection_objects_children(ten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDisplayViewLayer::add_view_layer(ListBase &tree, TreeElement &parent)
|
||||||
|
{
|
||||||
|
/* First layer collection is for master collection, don't show it. */
|
||||||
|
LayerCollection *lc = static_cast<LayerCollection *>(view_layer_->layer_collections.first);
|
||||||
|
if (lc == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_layer_collections_recursive(tree, lc->layer_collections, parent);
|
||||||
|
if (show_objects_) {
|
||||||
|
add_layer_collection_objects(tree, *lc, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDisplayViewLayer::add_layer_collections_recursive(ListBase &tree,
|
||||||
|
ListBase &layer_collections,
|
||||||
|
TreeElement &parent_ten)
|
||||||
|
{
|
||||||
|
for (LayerCollection *lc : List<LayerCollection>(layer_collections)) {
|
||||||
|
const bool exclude = (lc->flag & LAYER_COLLECTION_EXCLUDE) != 0;
|
||||||
|
TreeElement *ten;
|
||||||
|
|
||||||
|
if (exclude && ((space_outliner_.show_restrict_flags & SO_RESTRICT_ENABLE) == 0)) {
|
||||||
|
ten = &parent_ten;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ID *id = &lc->collection->id;
|
||||||
|
ten = outliner_add_element(
|
||||||
|
&space_outliner_, &tree, id, &parent_ten, TSE_LAYER_COLLECTION, 0);
|
||||||
|
|
||||||
|
ten->name = id->name + 2;
|
||||||
|
ten->directdata = lc;
|
||||||
|
|
||||||
|
/* Open by default, except linked collections, which may contain many elements. */
|
||||||
|
TreeStoreElem *tselem = TREESTORE(ten);
|
||||||
|
if (!(tselem->used || ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY(id))) {
|
||||||
|
tselem->flag &= ~TSE_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exclude || (lc->runtime_flag & LAYER_COLLECTION_VISIBLE_VIEW_LAYER) == 0) {
|
||||||
|
ten->flag |= TE_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_layer_collections_recursive(ten->subtree, lc->layer_collections, *ten);
|
||||||
|
if (!exclude && show_objects_) {
|
||||||
|
add_layer_collection_objects(ten->subtree, *lc, *ten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDisplayViewLayer::add_layer_collection_objects(ListBase &tree,
|
||||||
|
LayerCollection &lc,
|
||||||
|
TreeElement &ten)
|
||||||
|
{
|
||||||
|
for (CollectionObject *cob : List<CollectionObject>(lc.collection->gobject)) {
|
||||||
|
Base *base = BKE_view_layer_base_find(view_layer_, cob->ob);
|
||||||
|
TreeElement *te_object = outliner_add_element(
|
||||||
|
&space_outliner_, &tree, base->object, &ten, 0, 0);
|
||||||
|
te_object->directdata = base;
|
||||||
|
|
||||||
|
if (!(base->flag & BASE_VISIBLE_VIEWLAYER)) {
|
||||||
|
te_object->flag |= TE_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TreeDisplayViewLayer::add_layer_collection_objects_children(TreeElement &collection_tree_elem)
|
||||||
|
{
|
||||||
|
/* Call helper to add children. */
|
||||||
|
ObjectsChildrenBuilder child_builder{space_outliner_};
|
||||||
|
child_builder(collection_tree_elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/** \name Object Children helper.
|
||||||
|
*
|
||||||
|
* Helper to add child objects to the sub-tree of their parent, recursively covering all nested
|
||||||
|
* collections.
|
||||||
|
*
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
ObjectsChildrenBuilder::ObjectsChildrenBuilder(SpaceOutliner &outliner) : outliner_(outliner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectsChildrenBuilder::operator()(TreeElement &collection_tree_elem)
|
||||||
|
{
|
||||||
|
object_tree_elements_lookup_create_recursive(&collection_tree_elem);
|
||||||
|
make_object_parent_hierarchy_collections();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a map from Object* to a list of TreeElement* matching the object.
|
||||||
|
*/
|
||||||
|
void ObjectsChildrenBuilder::object_tree_elements_lookup_create_recursive(TreeElement *te_parent)
|
||||||
|
{
|
||||||
|
for (TreeElement *te : List<TreeElement>(te_parent->subtree)) {
|
||||||
|
TreeStoreElem *tselem = TREESTORE(te);
|
||||||
|
|
||||||
|
if (tselem->type == TSE_LAYER_COLLECTION) {
|
||||||
|
object_tree_elements_lookup_create_recursive(te);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tselem->type == 0 && te->idcode == ID_OB) {
|
||||||
|
Object *ob = (Object *)tselem->id;
|
||||||
|
/* Lookup children or add new, empty children vector. */
|
||||||
|
Vector<TreeElement *> &tree_elements = object_tree_elements_map_.lookup_or_add(ob, {});
|
||||||
|
|
||||||
|
tree_elements.append(te);
|
||||||
|
object_tree_elements_lookup_create_recursive(te);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For all objects in the tree, lookup the parent in this map,
|
||||||
|
* and move or add tree elements as needed.
|
||||||
|
*/
|
||||||
|
void ObjectsChildrenBuilder::make_object_parent_hierarchy_collections()
|
||||||
|
{
|
||||||
|
for (ObjectTreeElementsMap::MutableItem item : object_tree_elements_map_.items()) {
|
||||||
|
Object *child = item.key;
|
||||||
|
|
||||||
|
if (child->parent == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<TreeElement *> &child_ob_tree_elements = item.value;
|
||||||
|
Vector<TreeElement *> *parent_ob_tree_elements = object_tree_elements_map_.lookup_ptr(
|
||||||
|
child->parent);
|
||||||
|
if (parent_ob_tree_elements == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TreeElement *parent_ob_tree_element : *parent_ob_tree_elements) {
|
||||||
|
TreeElement *parent_ob_collection_tree_element = nullptr;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
/* We always want to remove the child from the direct collection its parent is nested under.
|
||||||
|
* This is particularly important when dealing with multi-level nesting (grandchildren). */
|
||||||
|
parent_ob_collection_tree_element = parent_ob_tree_element->parent;
|
||||||
|
while (!ELEM(TREESTORE(parent_ob_collection_tree_element)->type,
|
||||||
|
TSE_VIEW_COLLECTION_BASE,
|
||||||
|
TSE_LAYER_COLLECTION)) {
|
||||||
|
parent_ob_collection_tree_element = parent_ob_collection_tree_element->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TreeElement *child_ob_tree_element : child_ob_tree_elements) {
|
||||||
|
if (child_ob_tree_element->parent == parent_ob_collection_tree_element) {
|
||||||
|
/* Move from the collection subtree into the parent object subtree. */
|
||||||
|
BLI_remlink(&parent_ob_collection_tree_element->subtree, child_ob_tree_element);
|
||||||
|
BLI_addtail(&parent_ob_tree_element->subtree, child_ob_tree_element);
|
||||||
|
child_ob_tree_element->parent = parent_ob_tree_element;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
/* We add the child in the tree even if it is not in the collection.
|
||||||
|
* We deliberately clear its sub-tree though, to make it less prominent. */
|
||||||
|
TreeElement *child_ob_tree_element = outliner_add_element(
|
||||||
|
&outliner_, &parent_ob_tree_element->subtree, child, parent_ob_tree_element, 0, 0);
|
||||||
|
outliner_free_tree(&child_ob_tree_element->subtree);
|
||||||
|
child_ob_tree_element->flag |= TE_CHILD_NOT_IN_COLLECTION;
|
||||||
|
child_ob_tree_elements.append(child_ob_tree_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \} */
|
||||||
|
|
||||||
|
} // namespace blender::ed::outliner
|
@@ -234,6 +234,9 @@ typedef enum eSpaceButtons_Flag {
|
|||||||
/** \name Outliner
|
/** \name Outliner
|
||||||
* \{ */
|
* \{ */
|
||||||
|
|
||||||
|
/* Defined in `outliner_intern.h`. */
|
||||||
|
typedef struct SpaceOutliner_Runtime SpaceOutliner_Runtime;
|
||||||
|
|
||||||
/* Outliner */
|
/* Outliner */
|
||||||
typedef struct SpaceOutliner {
|
typedef struct SpaceOutliner {
|
||||||
SpaceLink *next, *prev;
|
SpaceLink *next, *prev;
|
||||||
@@ -276,6 +279,8 @@ typedef struct SpaceOutliner {
|
|||||||
* Pointers to treestore elements, grouped by (id, type, nr)
|
* Pointers to treestore elements, grouped by (id, type, nr)
|
||||||
* in hashtable for faster searching */
|
* in hashtable for faster searching */
|
||||||
void *treehash;
|
void *treehash;
|
||||||
|
|
||||||
|
SpaceOutliner_Runtime *runtime;
|
||||||
} SpaceOutliner;
|
} SpaceOutliner;
|
||||||
|
|
||||||
/* SpaceOutliner.flag */
|
/* SpaceOutliner.flag */
|
||||||
|
Reference in New Issue
Block a user