Outliner: Modifier/constraint/shaderfx drag and drop operator

This adds an operator to allow drag and drop of modifiers, constraints,
and shader effects within the outliner. Referred to as "data stack" in
the code for simplicity.

The following operations are allowed:
* Reordering within an object or bone
* Copying a single modifier/constraint/effect to another object or bone
* Copying (linking) all modifiers/constraints/effects to another object
  or bone.

This complements the recent work done for panel-based modifier layouts
by allowing reordering in the outliner. It also makes it simple to copy
a single modifier/constraint/effect to another object.

Differential Revision: https://developer.blender.org/D8642
This commit is contained in:
Nathan Craddock 2020-09-15 15:23:08 -06:00
parent 583354e9e9
commit 1572da858d
Notes: blender-bot 2023-02-14 07:31:34 +01:00
Referenced by issue #77408, Continued Outliner Improvements Design
5 changed files with 511 additions and 15 deletions

View File

@ -26,7 +26,9 @@
#include "MEM_guardedalloc.h"
#include "DNA_collection_types.h"
#include "DNA_constraint_types.h"
#include "DNA_material_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_space_types.h"
@ -36,6 +38,7 @@
#include "BLT_translation.h"
#include "BKE_collection.h"
#include "BKE_constraint.h"
#include "BKE_context.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
@ -44,6 +47,7 @@
#include "BKE_object.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_shader_fx.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
@ -65,6 +69,8 @@
#include "outliner_intern.h"
static Collection *collection_parent_from_ID(ID *id);
/* ******************** Drop Target Find *********************** */
static TreeElement *outliner_dropzone_element(TreeElement *te,
@ -146,7 +152,8 @@ static TreeElement *outliner_drop_insert_find(bContext *C,
const float margin = UI_UNIT_Y * (1.0f / 4);
if (view_mval[1] < (te_hovered->ys + margin)) {
if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner)) {
if (TSELEM_OPEN(TREESTORE(te_hovered), space_outliner) &&
!BLI_listbase_is_empty(&te_hovered->subtree)) {
/* inserting after a open item means we insert into it, but as first child */
if (BLI_listbase_is_empty(&te_hovered->subtree)) {
*r_insert_type = TE_INSERT_INTO;
@ -183,20 +190,37 @@ static TreeElement *outliner_drop_insert_find(bContext *C,
return NULL;
}
static Collection *outliner_collection_from_tree_element_and_parents(TreeElement *te,
TreeElement **r_te)
typedef bool (*CheckTypeFn)(TreeElement *te);
static TreeElement *outliner_data_from_tree_element_and_parents(CheckTypeFn check_type,
TreeElement *te)
{
while (te != NULL) {
Collection *collection = outliner_collection_from_tree_element(te);
if (collection) {
*r_te = te;
return collection;
if (check_type(te)) {
return te;
}
te = te->parent;
}
return NULL;
}
static bool is_collection_element(TreeElement *te)
{
return outliner_is_collection_tree_element(te);
}
static bool is_object_element(TreeElement *te)
{
TreeStoreElem *tselem = TREESTORE(te);
return tselem->type == 0 && te->idcode == ID_OB;
}
static bool is_pchan_element(TreeElement *te)
{
TreeStoreElem *tselem = TREESTORE(te);
return tselem->type == TSE_POSE_CHANNEL;
}
static TreeElement *outliner_drop_insert_collection_find(bContext *C,
const wmEvent *event,
TreeElementInsertType *r_insert_type)
@ -206,11 +230,12 @@ static TreeElement *outliner_drop_insert_collection_find(bContext *C,
return NULL;
}
TreeElement *collection_te;
Collection *collection = outliner_collection_from_tree_element_and_parents(te, &collection_te);
if (!collection) {
TreeElement *collection_te = outliner_data_from_tree_element_and_parents(is_collection_element,
te);
if (!collection_te) {
return NULL;
}
Collection *collection = outliner_collection_from_tree_element(collection_te);
if (collection_te != te) {
*r_insert_type = TE_INSERT_INTO;
@ -224,6 +249,30 @@ static TreeElement *outliner_drop_insert_collection_find(bContext *C,
return collection_te;
}
static int outliner_get_insert_index(TreeElement *drag_te,
TreeElement *drop_te,
TreeElementInsertType insert_type,
ListBase *listbase)
{
/* Find the element to insert after. NULL is the start of the list. */
if (drag_te->index < drop_te->index) {
if (insert_type == TE_INSERT_BEFORE) {
drop_te = drop_te->prev;
}
}
else {
if (insert_type == TE_INSERT_AFTER) {
drop_te = drop_te->next;
}
}
if (drop_te == NULL) {
return 0;
}
return BLI_findindex(listbase, drop_te->directdata);
}
/* ******************** Parent Drop Operator *********************** */
static bool parent_drop_allowed(TreeElement *te, Object *potential_child)
@ -616,6 +665,413 @@ void OUTLINER_OT_material_drop(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
}
/* ******************** Data Stack Drop Operator *********************** */
/* A generic operator to allow drag and drop for modifiers, constraints,
* and shader effects which all share the same UI stack layout.
*
* The following operations are allowed:
* - Reordering within an object.
* - Copying a single modifier/constraint/effect to another object.
* - Copying (linking) an object's modifiers/constraints/effects to another. */
typedef enum eDataStackDropAction {
DATA_STACK_DROP_REORDER,
DATA_STACK_DROP_COPY,
DATA_STACK_DROP_LINK,
} eDataStackDropAction;
typedef struct StackDropData {
Object *ob_parent;
bPoseChannel *pchan_parent;
TreeStoreElem *drag_tselem;
void *drag_directdata;
int drag_index;
eDataStackDropAction drop_action;
TreeElement *drop_te;
TreeElementInsertType insert_type;
} StackDropData;
static void datastack_drop_data_init(wmDrag *drag,
Object *ob,
bPoseChannel *pchan,
TreeElement *te,
TreeStoreElem *tselem,
void *directdata)
{
StackDropData *drop_data = MEM_callocN(sizeof(*drop_data), "datastack drop data");
drop_data->ob_parent = ob;
drop_data->pchan_parent = pchan;
drop_data->drag_tselem = tselem;
drop_data->drag_directdata = directdata;
drop_data->drag_index = te->index;
drag->poin = drop_data;
drag->flags |= WM_DRAG_FREE_DATA;
}
static bool datastack_drop_init(bContext *C, const wmEvent *event, StackDropData *drop_data)
{
if (!ELEM(drop_data->drag_tselem->type,
TSE_MODIFIER,
TSE_MODIFIER_BASE,
TSE_CONSTRAINT,
TSE_CONSTRAINT_BASE,
TSE_GPENCIL_EFFECT,
TSE_GPENCIL_EFFECT_BASE)) {
return false;
}
TreeElement *te_target = outliner_drop_insert_find(C, event, &drop_data->insert_type);
if (!te_target) {
return false;
}
TreeStoreElem *tselem_target = TREESTORE(te_target);
if (drop_data->drag_tselem == tselem_target) {
return false;
}
Object *ob = NULL;
TreeElement *object_te = outliner_data_from_tree_element_and_parents(is_object_element,
te_target);
if (object_te) {
ob = (Object *)TREESTORE(object_te)->id;
}
bPoseChannel *pchan = NULL;
TreeElement *pchan_te = outliner_data_from_tree_element_and_parents(is_pchan_element, te_target);
if (pchan_te) {
pchan = (bPoseChannel *)pchan_te->directdata;
}
if (pchan) {
ob = NULL;
}
if (ob && ID_IS_LINKED(&ob->id)) {
return false;
}
/* Drag a base for linking. */
if (ELEM(drop_data->drag_tselem->type,
TSE_MODIFIER_BASE,
TSE_CONSTRAINT_BASE,
TSE_GPENCIL_EFFECT_BASE)) {
drop_data->insert_type = TE_INSERT_INTO;
drop_data->drop_action = DATA_STACK_DROP_LINK;
if (pchan && pchan != drop_data->pchan_parent) {
drop_data->drop_te = pchan_te;
tselem_target = TREESTORE(pchan_te);
}
else if (ob && ob != drop_data->ob_parent) {
drop_data->drop_te = object_te;
tselem_target = TREESTORE(object_te);
}
else {
return false;
}
}
else if (ob || pchan) {
/* Drag a single item. */
if (pchan && pchan != drop_data->pchan_parent) {
drop_data->insert_type = TE_INSERT_INTO;
drop_data->drop_action = DATA_STACK_DROP_COPY;
drop_data->drop_te = pchan_te;
tselem_target = TREESTORE(pchan_te);
}
else if (ob && ob != drop_data->ob_parent) {
drop_data->insert_type = TE_INSERT_INTO;
drop_data->drop_action = DATA_STACK_DROP_COPY;
drop_data->drop_te = object_te;
tselem_target = TREESTORE(object_te);
}
else if (tselem_target->type == drop_data->drag_tselem->type) {
if (drop_data->insert_type == TE_INSERT_INTO) {
return false;
}
drop_data->drop_action = DATA_STACK_DROP_REORDER;
drop_data->drop_te = te_target;
}
else {
return false;
}
}
else {
return false;
}
return true;
}
/* Ensure that grease pencil and object data remain separate. */
static bool datastack_drop_are_types_valid(StackDropData *drop_data)
{
TreeStoreElem *tselem = TREESTORE(drop_data->drop_te);
Object *ob_parent = drop_data->ob_parent;
Object *ob_dst = (Object *)tselem->id;
/* Don't allow data to be moved between objects and bones. */
if (tselem->type == TSE_CONSTRAINT) {
}
else if ((drop_data->pchan_parent && tselem->type != TSE_POSE_CHANNEL) ||
(!drop_data->pchan_parent && tselem->type == TSE_POSE_CHANNEL)) {
return false;
}
switch (drop_data->drag_tselem->type) {
case TSE_MODIFIER_BASE:
case TSE_MODIFIER:
if (ob_parent->type == OB_GPENCIL) {
return ob_dst->type == OB_GPENCIL;
}
else if (ob_parent->type != OB_GPENCIL) {
return ob_dst->type != OB_GPENCIL;
}
break;
case TSE_CONSTRAINT_BASE:
case TSE_CONSTRAINT:
break;
case TSE_GPENCIL_EFFECT_BASE:
case TSE_GPENCIL_EFFECT:
return ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL;
break;
}
return true;
}
static bool datastack_drop_poll(bContext *C,
wmDrag *drag,
const wmEvent *event,
const char **r_tooltip)
{
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
ARegion *region = CTX_wm_region(C);
bool changed = outliner_flag_set(&space_outliner->tree, TSE_HIGHLIGHTED | TSE_DRAG_ANY, false);
StackDropData *drop_data = drag->poin;
if (!drop_data) {
return false;
}
if (!datastack_drop_init(C, event, drop_data)) {
return false;
}
if (!datastack_drop_are_types_valid(drop_data)) {
return false;
}
TreeStoreElem *tselem_target = TREESTORE(drop_data->drop_te);
switch (drop_data->insert_type) {
case TE_INSERT_BEFORE:
tselem_target->flag |= TSE_DRAG_BEFORE;
break;
case TE_INSERT_AFTER:
tselem_target->flag |= TSE_DRAG_AFTER;
break;
case TE_INSERT_INTO:
tselem_target->flag |= TSE_DRAG_INTO;
break;
}
switch (drop_data->drop_action) {
case DATA_STACK_DROP_REORDER:
*r_tooltip = TIP_("Reorder");
break;
case DATA_STACK_DROP_COPY:
if (drop_data->pchan_parent) {
*r_tooltip = TIP_("Copy to bone");
}
else {
*r_tooltip = TIP_("Copy to object");
}
break;
case DATA_STACK_DROP_LINK:
if (drop_data->pchan_parent) {
*r_tooltip = TIP_("Link all to bone");
}
else {
*r_tooltip = TIP_("Link all to object");
}
break;
}
if (changed) {
ED_region_tag_redraw_no_rebuild(region);
}
return true;
}
static void datastack_drop_link(bContext *C, StackDropData *drop_data)
{
Main *bmain = CTX_data_main(C);
TreeStoreElem *tselem = TREESTORE(drop_data->drop_te);
Object *ob_dst = (Object *)tselem->id;
switch (drop_data->drag_tselem->type) {
case TSE_MODIFIER_BASE:
ED_object_modifier_link(C, ob_dst, drop_data->ob_parent);
break;
case TSE_CONSTRAINT_BASE: {
ListBase *src;
if (drop_data->pchan_parent) {
src = &drop_data->pchan_parent->constraints;
}
else {
src = &drop_data->ob_parent->constraints;
}
ListBase *dst;
if (tselem->type == TSE_POSE_CHANNEL) {
bPoseChannel *pchan = (bPoseChannel *)drop_data->drop_te->directdata;
dst = &pchan->constraints;
}
else {
dst = &ob_dst->constraints;
}
ED_object_constraint_link(bmain, ob_dst, dst, src);
break;
}
case TSE_GPENCIL_EFFECT_BASE:
if (ob_dst->type != OB_GPENCIL) {
return;
}
ED_object_shaderfx_link(ob_dst, drop_data->ob_parent);
break;
}
}
static void datastack_drop_copy(bContext *C, StackDropData *drop_data)
{
Main *bmain = CTX_data_main(C);
TreeStoreElem *tselem = TREESTORE(drop_data->drop_te);
Object *ob_dst = (Object *)tselem->id;
switch (drop_data->drag_tselem->type) {
case TSE_MODIFIER:
if (drop_data->ob_parent->type == OB_GPENCIL && ob_dst->type == OB_GPENCIL) {
ED_object_gpencil_modifier_copy_to_object(ob_dst, drop_data->drag_directdata);
}
else if (drop_data->ob_parent->type != OB_GPENCIL && ob_dst->type != OB_GPENCIL) {
ED_object_modifier_copy_to_object(
ob_dst, drop_data->ob_parent, drop_data->drag_directdata);
}
break;
case TSE_CONSTRAINT:
if (tselem->type == TSE_POSE_CHANNEL) {
ED_object_constraint_copy_for_pose(
bmain, ob_dst, drop_data->drop_te->directdata, drop_data->drag_directdata);
}
else {
ED_object_constraint_copy_for_object(bmain, ob_dst, drop_data->drag_directdata);
}
break;
case TSE_GPENCIL_EFFECT: {
if (ob_dst->type != OB_GPENCIL) {
return;
}
ED_object_shaderfx_copy(ob_dst, drop_data->drag_directdata);
break;
}
}
}
static void datastack_drop_reorder(bContext *C, ReportList *reports, StackDropData *drop_data)
{
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
TreeElement *drag_te = outliner_find_tree_element(&space_outliner->tree, drop_data->drag_tselem);
if (!drag_te) {
return;
}
TreeElement *drop_te = drop_data->drop_te;
TreeElementInsertType insert_type = drop_data->insert_type;
Object *ob = drop_data->ob_parent;
int index = 0;
switch (drop_data->drag_tselem->type) {
case TSE_MODIFIER:
if (ob->type == OB_GPENCIL) {
index = outliner_get_insert_index(
drag_te, drop_te, insert_type, &ob->greasepencil_modifiers);
ED_object_gpencil_modifier_move_to_index(reports, ob, drop_data->drag_directdata, index);
}
else if (ob->type != OB_GPENCIL) {
index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->modifiers);
ED_object_modifier_move_to_index(reports, ob, drop_data->drag_directdata, index);
}
break;
case TSE_CONSTRAINT:
if (drop_data->pchan_parent) {
index = outliner_get_insert_index(
drag_te, drop_te, insert_type, &drop_data->pchan_parent->constraints);
}
else {
index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->constraints);
}
ED_object_constraint_move_to_index(ob, drop_data->drag_directdata, index);
break;
case TSE_GPENCIL_EFFECT:
index = outliner_get_insert_index(drag_te, drop_te, insert_type, &ob->shader_fx);
ED_object_shaderfx_move_to_index(reports, ob, drop_data->drag_directdata, index);
}
}
static int datastack_drop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (event->custom != EVT_DATA_DRAGDROP) {
return OPERATOR_CANCELLED;
}
ListBase *lb = event->customdata;
wmDrag *drag = lb->first;
StackDropData *drop_data = drag->poin;
switch (drop_data->drop_action) {
case DATA_STACK_DROP_LINK:
datastack_drop_link(C, drop_data);
break;
case DATA_STACK_DROP_COPY:
datastack_drop_copy(C, drop_data);
break;
case DATA_STACK_DROP_REORDER:
datastack_drop_reorder(C, op->reports, drop_data);
break;
}
return OPERATOR_FINISHED;
}
void OUTLINER_OT_datastack_drop(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Data Stack Drop";
ot->description = "Copy or reorder modifiers, constraints, and effects";
ot->idname = "OUTLINER_OT_datastack_drop";
/* api callbacks */
ot->invoke = datastack_drop_invoke;
ot->poll = ED_operator_outliner_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
}
/* ******************** Collection Drop Operator *********************** */
typedef struct CollectionDrop {
@ -882,7 +1338,8 @@ static int outliner_item_drag_drop_invoke(bContext *C,
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
}
TreeElementIcon data = tree_element_get_icon(TREESTORE(te), te);
TreeStoreElem *tselem = TREESTORE(te);
TreeElementIcon data = tree_element_get_icon(tselem, te);
if (!data.drag_id) {
return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH);
}
@ -910,13 +1367,25 @@ static int outliner_item_drag_drop_invoke(bContext *C,
wmDrag *drag = WM_event_start_drag(C, data.icon, WM_DRAG_ID, NULL, 0.0, WM_DRAG_NOP);
if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) {
if (ELEM(tselem->type,
TSE_MODIFIER,
TSE_MODIFIER_BASE,
TSE_CONSTRAINT,
TSE_CONSTRAINT_BASE,
TSE_GPENCIL_EFFECT,
TSE_GPENCIL_EFFECT_BASE)) {
TreeElement *te_bone = NULL;
bPoseChannel *pchan = outliner_find_parent_bone(te, &te_bone);
datastack_drop_data_init(drag, (Object *)tselem->id, pchan, te, tselem, te->directdata);
}
else if (ELEM(GS(data.drag_id->name), ID_OB, ID_GR)) {
/* For collections and objects we cheat and drag all selected. */
/* Only drag element under mouse if it was not selected before. */
if ((TREESTORE(te)->flag & TSE_SELECTED) == 0) {
if ((tselem->flag & TSE_SELECTED) == 0) {
outliner_flag_set(&space_outliner->tree, TSE_SELECTED, 0);
TREESTORE(te)->flag |= TSE_SELECTED;
tselem->flag |= TSE_SELECTED;
}
/* Gather all selected elements. */
@ -995,7 +1464,7 @@ static int outliner_item_drag_drop_invoke(bContext *C,
WM_drag_add_ID(drag, data.drag_id, data.drag_parent);
}
ED_outliner_select_sync_from_all_tag(C);
ED_outliner_select_sync_from_outliner(C, space_outliner);
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
}
@ -1027,5 +1496,6 @@ void outliner_dropboxes(void)
WM_dropbox_add(lb, "OUTLINER_OT_parent_clear", parent_clear_poll, NULL);
WM_dropbox_add(lb, "OUTLINER_OT_scene_drop", scene_drop_poll, NULL);
WM_dropbox_add(lb, "OUTLINER_OT_material_drop", material_drop_poll, NULL);
WM_dropbox_add(lb, "OUTLINER_OT_datastack_drop", datastack_drop_poll, NULL);
WM_dropbox_add(lb, "OUTLINER_OT_collection_drop", collection_drop_poll, NULL);
}

View File

@ -2024,9 +2024,11 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
break;
case TSE_CONSTRAINT_BASE:
data.icon = ICON_CONSTRAINT;
data.drag_id = tselem->id;
break;
case TSE_CONSTRAINT: {
bConstraint *con = te->directdata;
data.drag_id = tselem->id;
switch ((eBConstraint_Types)con->type) {
case CONSTRAINT_TYPE_CAMERASOLVER:
data.icon = ICON_CON_CAMERASOLVER;
@ -2121,6 +2123,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
}
case TSE_MODIFIER_BASE:
data.icon = ICON_MODIFIER_DATA;
data.drag_id = tselem->id;
break;
case TSE_LINKED_OB:
data.icon = ICON_OBJECT_DATA;
@ -2130,6 +2133,8 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
break;
case TSE_MODIFIER: {
Object *ob = (Object *)tselem->id;
data.drag_id = tselem->id;
if (ob->type != OB_GPENCIL) {
ModifierData *md = BLI_findlink(&ob->modifiers, tselem->nr);
switch ((ModifierType)md->type) {

View File

@ -276,6 +276,8 @@ eOLDrawState tree_element_active(struct bContext *C,
const eOLSetState set,
const bool handle_all_types);
struct bPoseChannel *outliner_find_parent_bone(TreeElement *te, TreeElement **r_bone_te);
void outliner_item_select(struct bContext *C,
struct SpaceOutliner *space_outliner,
struct TreeElement *te,
@ -387,6 +389,7 @@ void OUTLINER_OT_parent_drop(struct wmOperatorType *ot);
void OUTLINER_OT_parent_clear(struct wmOperatorType *ot);
void OUTLINER_OT_scene_drop(struct wmOperatorType *ot);
void OUTLINER_OT_material_drop(struct wmOperatorType *ot);
void OUTLINER_OT_datastack_drop(struct wmOperatorType *ot);
void OUTLINER_OT_collection_drop(struct wmOperatorType *ot);
/* ...................................................... */

View File

@ -88,6 +88,7 @@ void outliner_operatortypes(void)
WM_operatortype_append(OUTLINER_OT_parent_clear);
WM_operatortype_append(OUTLINER_OT_scene_drop);
WM_operatortype_append(OUTLINER_OT_material_drop);
WM_operatortype_append(OUTLINER_OT_datastack_drop);
WM_operatortype_append(OUTLINER_OT_collection_drop);
/* collections */

View File

@ -1032,6 +1032,23 @@ eOLDrawState tree_element_type_active(bContext *C,
return OL_DRAWSEL_NONE;
}
bPoseChannel *outliner_find_parent_bone(TreeElement *te, TreeElement **r_bone_te)
{
TreeStoreElem *tselem;
te = te->parent;
while (te) {
tselem = TREESTORE(te);
if (tselem->type == TSE_POSE_CHANNEL) {
*r_bone_te = te;
return (bPoseChannel *)te->directdata;
}
te = te->parent;
}
return NULL;
}
/* ================================================ */
/**