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:
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
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue