UI: support jumping to target object/bone

Complex rigs are built from many bones (often overlapping)
connected by constraints.

When investigating or debugging such rigs one often wants to switch to
the target of a constraint, or a parent bone, but it is difficult to do
manually due to overlap confusion.

This adds a right click menu option that automatically selects
and makes the target object or bone active for UI fields where a
suitable reference is readily available.
This commit is contained in:
Alexander Gavrilov 2018-11-20 17:34:56 +11:00 committed by Campbell Barton
parent 93f82698e7
commit d227c58e3e
Notes: blender-bot 2023-02-14 03:03:03 +01:00
Referenced by commit 2e043c266b, Fix: "Jump To Target" showing up in workspace context menu
5 changed files with 343 additions and 11 deletions

View File

@ -302,6 +302,11 @@ const struct EnumPropertyItem *ED_object_vgroup_selection_itemf_helper(
void ED_object_check_force_modifiers(
struct Main *bmain, struct Scene *scene, struct Object *object);
struct Base *ED_object_find_first_by_data_id(struct ViewLayer *view_layer, struct ID *id);
bool ED_object_jump_to_object(struct bContext *C, struct Object *ob);
bool ED_object_jump_to_bone(struct bContext *C, struct Object *ob, const char *bone_name);
/* object_facemap_ops.c */
void ED_object_facemap_face_add(struct Object *ob, struct bFaceMap *fmap, int facenum);
void ED_object_facemap_face_remove(struct Object *ob, struct bFaceMap *fmap, int facenum);

View File

@ -361,7 +361,12 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
}
if (but->type == UI_BTYPE_TAB) {
const bool is_disabled = but->flag & UI_BUT_DISABLED;
if (is_disabled) {
/* Suppress editing commands. */
}
else if (but->type == UI_BTYPE_TAB) {
uiButTab *tab = (uiButTab *)but;
if (tab->menu) {
UI_menutype_draw(C, tab->menu, layout);
@ -639,6 +644,17 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
}
}
/* Pointer properties and string properties with prop_search support jumping to target object/bone. */
if (but->rnapoin.data && but->rnaprop) {
const PropertyType type = RNA_property_type(but->rnaprop);
if ((type == PROP_POINTER) || (type == PROP_STRING && but->type == UI_BTYPE_SEARCH_MENU && but->search_func == ui_rna_collection_search_cb)) {
uiItemO(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Jump To Target"),
ICON_NONE, "UI_OT_jump_to_target_button");
uiItemS(layout);
}
}
/* Favorites Menu */
if (ui_but_is_user_menu_compatible(C, but)) {
uiBlock *block = uiLayoutGetBlock(layout);

View File

@ -6614,6 +6614,17 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
return WM_UI_HANDLER_BREAK;
}
/* handle menu */
if ((event->type == RIGHTMOUSE) &&
!IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
(event->val == KM_PRESS))
{
/* RMB has two options now */
if (ui_popup_context_menu_for_button(C, but)) {
return WM_UI_HANDLER_BREAK;
}
}
if (is_disabled) {
return WM_UI_HANDLER_CONTINUE;
}
@ -6627,16 +6638,6 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
if (event->type == EVT_DROP) {
ui_but_drop(C, event, but, data);
}
/* handle menu */
else if ((event->type == RIGHTMOUSE) &&
!IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
(event->val == KM_PRESS))
{
/* RMB has two options now */
if (ui_popup_context_menu_for_button(C, but)) {
return WM_UI_HANDLER_BREAK;
}
}
}
if (but->flag & UI_BUT_DISABLED) {

View File

@ -31,6 +31,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_armature_types.h"
#include "DNA_screen_types.h"
#include "DNA_text_types.h" /* for UI_OT_reports_to_text */
#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */
@ -65,6 +66,7 @@
#include "WM_api.h"
#include "WM_types.h"
#include "ED_object.h"
#include "ED_paint.h"
/* only for UI_OT_editsource */
@ -802,6 +804,151 @@ static void UI_OT_copy_to_selected_button(wmOperatorType *ot)
RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array");
}
/* -------------------------------------------------------------------- */
/** \name Jump to Target Operator
* \{ */
/** Jump to the object or bone referenced by the pointer, or check if it is possible. */
static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll)
{
if (RNA_pointer_is_null(&ptr)) {
return false;
}
/* Verify pointer type. */
char bone_name[MAXBONENAME];
const StructRNA *target_type = NULL;
if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) {
RNA_string_get(&ptr, "name", bone_name);
if (bone_name[0] != '\0') {
target_type = &RNA_Bone;
}
}
else if (RNA_struct_is_a(ptr.type, &RNA_Object)) {
target_type = &RNA_Object;
}
if (target_type == NULL) {
return false;
}
/* Find the containing Object. */
ViewLayer *view_layer = CTX_data_view_layer(C);
Base *base = NULL;
const short id_type = GS(((ID *)ptr.id.data)->name);
if (id_type == ID_OB) {
base = BKE_view_layer_base_find(view_layer, ptr.id.data);
}
else if (OB_DATA_SUPPORT_ID(id_type)) {
base = ED_object_find_first_by_data_id(view_layer, ptr.id.data);
}
bool ok = false;
if ((base == NULL) ||
((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE)))
{
/* pass */
}
else if (poll) {
ok = true;
}
else {
/* Select and activate the target. */
if (target_type == &RNA_Bone) {
ok = ED_object_jump_to_bone(C, base->object, bone_name);
}
else if (target_type == &RNA_Object) {
ok = ED_object_jump_to_object(C, base->object);
}
else {
BLI_assert(0);
}
}
return ok;
}
/**
* Jump to the object or bone referred to by the current UI field value.
*
* \note quite heavy for a poll callback, but the operator is only
* used as a right click menu item for certain UI field types, and
* this will fail quickly if the context is completely unsuitable.
*/
static bool jump_to_target_button(bContext *C, bool poll)
{
PointerRNA ptr, target_ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
/* If there is a valid property... */
if (ptr.data && prop) {
const PropertyType type = RNA_property_type(prop);
/* For pointer properties, use their value directly. */
if (type == PROP_POINTER) {
target_ptr = RNA_property_pointer_get(&ptr, prop);
return jump_to_target_ptr(C, target_ptr, poll);
}
/* For string properties with prop_search, look up the search collection item. */
else if (type == PROP_STRING) {
const uiBut *but = UI_context_active_but_get(C);
if (but->type == UI_BTYPE_SEARCH_MENU && but->search_func == ui_rna_collection_search_cb) {
uiRNACollectionSearch *coll_search = but->search_arg;
char str_buf[MAXBONENAME];
char *str_ptr = RNA_property_string_get_alloc(&ptr, prop, str_buf, sizeof(str_buf), NULL);
int found = RNA_property_collection_lookup_string(&coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr);
if (str_ptr != str_buf) {
MEM_freeN(str_ptr);
}
if (found) {
return jump_to_target_ptr(C, target_ptr, poll);
}
}
}
}
return false;
}
static bool jump_to_target_button_poll(bContext *C)
{
return jump_to_target_button(C, true);
}
static int jump_to_target_button_exec(bContext *C, wmOperator *UNUSED(op))
{
bool success = jump_to_target_button(C, false);
return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void UI_OT_jump_to_target_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Jump To Target";
ot->idname = "UI_OT_jump_to_target_button";
ot->description = "Switch to the target object or bone";
/* callbacks */
ot->poll = jump_to_target_button_poll;
ot->exec = jump_to_target_button_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* Reports to Textblock Operator ------------------------ */
/* FIXME: this is just a temporary operator so that we can see all the reports somewhere
@ -1410,6 +1557,7 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_override_type_set_button);
WM_operatortype_append(UI_OT_override_remove_button);
WM_operatortype_append(UI_OT_copy_to_selected_button);
WM_operatortype_append(UI_OT_jump_to_target_button);
WM_operatortype_append(UI_OT_reports_to_textblock); /* XXX: temp? */
WM_operatortype_append(UI_OT_drop_color);
#ifdef WITH_PYTHON

View File

@ -51,6 +51,8 @@
#include "BLT_translation.h"
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_collection.h"
#include "BKE_context.h"
#include "BKE_deform.h"
@ -70,6 +72,7 @@
#include "WM_api.h"
#include "WM_types.h"
#include "ED_armature.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
@ -159,6 +162,165 @@ void ED_object_base_activate(bContext *C, Base *base)
DEG_id_tag_update(&CTX_data_scene(C)->id, DEG_TAG_SELECT_UPDATE);
}
/********************** Jump To Object Utilities **********************/
static int get_base_select_priority(Base *base)
{
if (base->flag & BASE_VISIBLE) {
if (base->flag & BASE_SELECTABLE) {
return 3;
}
else {
return 2;
}
}
else {
return 1;
}
}
/**
* If id is not already an Object, try to find an object that uses it as data.
* Prefers active, then selected, then visible/selectable.
*/
Base *ED_object_find_first_by_data_id(ViewLayer *view_layer, ID *id)
{
BLI_assert(OB_DATA_SUPPORT_ID(GS(id->name)));
/* Try active object. */
Base *basact = view_layer->basact;
if (basact && basact->object && basact->object->data == id) {
return basact;
}
/* Try all objects. */
Base *base_best = NULL;
int priority_best = 0;
for (Base *base = view_layer->object_bases.first; base; base = base->next) {
if (base->object && base->object->data == id) {
if (base->flag & BASE_SELECTED) {
return base;
}
else {
int priority_test = get_base_select_priority(base);
if (priority_test > priority_best) {
priority_best = priority_test;
base_best = base;
}
}
}
}
return base_best;
}
/**
* Select and make the target object active in the view layer.
* If already selected, selection isn't changed.
*
* \returns false if not found in current view layer
*/
bool ED_object_jump_to_object(bContext *C, Object *ob)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
Base *base = BKE_view_layer_base_find(view_layer, ob);
if (base == NULL) {
return false;
}
if (view_layer->basact != base) {
/* Select if not selected. */
if (!(base->flag & BASE_SELECTED)) {
ED_object_base_deselect_all_visible(view_layer);
if (base->flag & BASE_VISIBLE) {
ED_object_base_select(base, BA_SELECT);
}
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, CTX_data_scene(C));
}
/* Make active if not active. */
ED_object_base_activate(C, base);
}
return true;
}
/**
* Select and make the target object and bone active.
* Switches to Pose mode if in Object mode so the selection is visible.
*
* \returns false if object not in layer, bone not found, or other error
*/
bool ED_object_jump_to_bone(bContext *C, Object *ob, const char *bone_name)
{
/* Verify it's a valid armature object. */
if (ob == NULL || ob->type != OB_ARMATURE) {
return false;
}
bArmature *arm = ob->data;
if (arm == NULL || GS(arm->id.name) != ID_AR) {
return false;
}
/* Activate the armature object. */
if (!ED_object_jump_to_object(C, ob)) {
return false;
}
/* Switch to pose mode from object mode. */
if (!ELEM(ob->mode, OB_MODE_EDIT, OB_MODE_POSE)) {
ED_object_mode_set(C, OB_MODE_POSE);
}
if (ob->mode == OB_MODE_EDIT && arm->edbo != NULL) {
/* In Edit mode select and activate the target Edit-Bone. */
EditBone *ebone = ED_armature_ebone_find_name(arm->edbo, bone_name);
if (ebone != NULL) {
ED_armature_edit_deselect_all(ob);
if (EBONE_SELECTABLE(arm, ebone)) {
ED_armature_ebone_select_set(ebone, true);
ED_armature_edit_sync_selection(arm->edbo);
}
if (EBONE_VISIBLE(arm, ebone)) {
arm->act_edbone = ebone;
}
ED_pose_bone_select_tag_update(ob);
return true;
}
}
else if (ob->mode == OB_MODE_POSE && ob->pose != NULL) {
/* In Pose mode select and activate the target Bone/Pose-Channel. */
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
if (pchan != NULL) {
ED_pose_deselect_all(ob, SEL_DESELECT, true);
if (PBONE_SELECTABLE(arm, pchan->bone)) {
ED_pose_bone_select(ob, pchan, true);
}
if (PBONE_VISIBLE(arm, pchan->bone)) {
arm->act_bone = pchan->bone;
}
ED_pose_bone_select_tag_update(ob);
return true;
}
}
return false;
}
/********************** Selection Operators **********************/
static bool objects_selectable_poll(bContext *C)