From fae19d7c927b5fb0e414576fcc7cf04ffc2d32e0 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Thu, 26 Sep 2024 10:05:09 +0200 Subject: [PATCH] Anim: Eyedropper for bone properties This adds an eyedropper button to properties where you can choose a bone. Clicking it activates the modal operation and you can pick: * Bones from the Outliner * Bones from the 3D Viewport if the Armature is in Pose Mode or Edit Mode ## Limitation Picking from the 3D viewport while in Object Mode doesn't work due to technical reasons. The selection buffer is only filled with pose bones if the armature is in pose mode. Using the picker while in object mode gives you the Armature object, not its bones. So you cannot use the bone picker to constrain an object to a bone. UNLESS you pin the panel with the object, go to pose mode and then pick. There is a warning printed for those cases to tell the user why this hasn't worked. Pull Request: https://projects.blender.org/blender/blender/pulls/121523 --- .../editors/armature/armature_intern.hh | 4 - source/blender/editors/include/ED_armature.hh | 4 + source/blender/editors/include/ED_outliner.hh | 8 + .../blender/editors/interface/CMakeLists.txt | 1 + .../interface/eyedroppers/eyedropper_bone.cc | 551 ++++++++++++++++++ .../eyedroppers/eyedropper_datablock.cc | 4 +- .../eyedroppers/eyedropper_intern.hh | 10 +- .../eyedroppers/interface_eyedropper.cc | 11 +- source/blender/editors/interface/interface.cc | 32 + .../editors/interface/interface_intern.hh | 3 + .../editors/interface/interface_ops.cc | 1 + .../editors/space_outliner/outliner_utils.cc | 42 ++ 12 files changed, 655 insertions(+), 16 deletions(-) create mode 100644 source/blender/editors/interface/eyedroppers/eyedropper_bone.cc diff --git a/source/blender/editors/armature/armature_intern.hh b/source/blender/editors/armature/armature_intern.hh index d19f1e8ea5f..f80d80d629a 100644 --- a/source/blender/editors/armature/armature_intern.hh +++ b/source/blender/editors/armature/armature_intern.hh @@ -266,10 +266,6 @@ void armature_tag_unselect(bArmature *arm); /** \name Selection Picking * \{ */ -EditBone *ED_armature_pick_ebone(bContext *C, const int xy[2], bool findunsel, Base **r_base); -bPoseChannel *ED_armature_pick_pchan(bContext *C, const int xy[2], bool findunsel, Base **r_base); -Bone *ED_armature_pick_bone(bContext *C, const int xy[2], bool findunsel, Base **r_base); - EditBone *ED_armature_pick_ebone_from_selectbuffer(blender::Span bases, const GPUSelectResult *hit_results, int hits, diff --git a/source/blender/editors/include/ED_armature.hh b/source/blender/editors/include/ED_armature.hh index d1790e686c5..dae012b9f71 100644 --- a/source/blender/editors/include/ED_armature.hh +++ b/source/blender/editors/include/ED_armature.hh @@ -340,3 +340,7 @@ void ED_mesh_deform_bind_callback(Object *object, float *vertexcos, int verts_num, float cagemat[4][4]); + +EditBone *ED_armature_pick_ebone(bContext *C, const int xy[2], bool findunsel, Base **r_base); +bPoseChannel *ED_armature_pick_pchan(bContext *C, const int xy[2], bool findunsel, Base **r_base); +Bone *ED_armature_pick_bone(bContext *C, const int xy[2], bool findunsel, Base **r_base); diff --git a/source/blender/editors/include/ED_outliner.hh b/source/blender/editors/include/ED_outliner.hh index b2aee00f556..d02b48697d5 100644 --- a/source/blender/editors/include/ED_outliner.hh +++ b/source/blender/editors/include/ED_outliner.hh @@ -13,6 +13,8 @@ struct ListBase; struct SpaceOutliner; struct bContext; struct ID; +struct Bone; +struct PointerRNA; bool ED_outliner_collections_editor_poll(bContext *C); @@ -28,6 +30,12 @@ void ED_outliner_selected_objects_get(const bContext *C, ListBase *objects); */ Base *ED_outliner_give_base_under_cursor(bContext *C, const int mval[2]); +/** + * Get the PointerRNA at the given coordinates. ONLY works for bone structs at the moment. Can be + * extended to work with other structs if the need arises. + */ +bool ED_outliner_give_rna_under_cursor(bContext *C, const int mval[2], PointerRNA *r_ptr); + /** * Functions for tagging outliner selection syncing is dirty from operators. */ diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 6275d7ba620..7ce205e229c 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -35,6 +35,7 @@ set(SRC eyedroppers/eyedropper_depth.cc eyedroppers/eyedropper_driver.cc eyedroppers/eyedropper_gpencil_color.cc + eyedroppers/eyedropper_bone.cc eyedroppers/eyedropper_grease_pencil_color.cc eyedroppers/interface_eyedropper.cc interface.cc diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_bone.cc b/source/blender/editors/interface/eyedroppers/eyedropper_bone.cc new file mode 100644 index 00000000000..d53e018773e --- /dev/null +++ b/source/blender/editors/interface/eyedroppers/eyedropper_bone.cc @@ -0,0 +1,551 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Eyedropper (bones) + */ + +#include "BKE_armature.hh" +#include "BKE_context.hh" +#include "BKE_idtype.hh" +#include "BKE_object.hh" +#include "BKE_report.hh" +#include "BKE_screen.hh" + +#include "BLT_translation.hh" + +#include "BLI_assert.h" +#include "BLI_math_vector.h" +#include "BLI_string.h" + +#include "RNA_access.hh" +#include "RNA_prototypes.hh" + +#include "ED_armature.hh" +#include "ED_outliner.hh" +#include "ED_screen.hh" +#include "ED_space_api.hh" +#include "ED_view3d.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "UI_interface.hh" +#include "UI_view2d.hh" + +#include "eyedropper_intern.hh" +#include "interface_intern.hh" + +namespace blender::ui { + +enum class SampleResult { + UNKNOWN_FAILURE, + SUCCESS, + NO_BONE_3DVIEW, + NO_BONE_OUTLINER, + WRONG_AREA, + WRONG_ARMATURE, +}; + +struct BoneDropper { + PointerRNA ptr; + PropertyRNA *prop; + PointerRNA search_ptr; + PropertyRNA *search_prop; + + bool is_undo; + + ScrArea *cursor_area; /* Area under the cursor. */ + ARegionType *area_region_type; + void *draw_handle_pixel; + int name_pos[2]; + char name[64]; +}; + +struct BoneSampleData { + SampleResult sample_result = SampleResult::UNKNOWN_FAILURE; + /* Either EditBone, bPoseChannel or Bone. */ + PointerRNA bone_rna; + char *name = nullptr; +}; + +static void datadropper_draw_cb(const bContext * /*C*/, ARegion * /*region*/, void *arg) +{ + BoneDropper *ddr = static_cast(arg); + eyedropper_draw_cursor_text_region(ddr->name_pos, ddr->name); +} + +static bool is_bone_dropper_valid(BoneDropper *bone_dropper) +{ + if ((bone_dropper->ptr.data == nullptr) || (bone_dropper->prop == nullptr)) { + return false; + } + if (!RNA_property_editable(&bone_dropper->ptr, bone_dropper->prop)) { + return false; + } + + PointerRNA owner_ptr = RNA_id_pointer_create(bone_dropper->search_ptr.owner_id); + if (RNA_type_to_ID_code(owner_ptr.type) != ID_AR) { + return false; + } + + return true; +} + +static int bonedropper_init(bContext *C, wmOperator *op) +{ + int index_dummy; + PointerRNA button_ptr; + PropertyRNA *button_prop; + uiBut *button = UI_context_active_but_prop_get(C, &button_ptr, &button_prop, &index_dummy); + + if (!button || button->type != UI_BTYPE_SEARCH_MENU) { + return false; + } + + BoneDropper *bone_dropper = MEM_cnew(__func__); + uiButSearch *search_button = (uiButSearch *)button; + bone_dropper->ptr = button_ptr; + bone_dropper->prop = button_prop; + bone_dropper->search_ptr = search_button->rnasearchpoin; + bone_dropper->search_prop = search_button->rnasearchprop; + if (!is_bone_dropper_valid(bone_dropper)) { + MEM_freeN(bone_dropper); + return false; + } + + op->customdata = bone_dropper; + + bone_dropper->is_undo = UI_but_flag_is_set(button, UI_BUT_UNDO); + + SpaceType *space_type = BKE_spacetype_from_id(SPACE_VIEW3D); + ARegionType *area_region_type = BKE_regiontype_from_id(space_type, RGN_TYPE_WINDOW); + bone_dropper->cursor_area = CTX_wm_area(C); + bone_dropper->area_region_type = area_region_type; + bone_dropper->draw_handle_pixel = ED_region_draw_cb_activate( + area_region_type, datadropper_draw_cb, bone_dropper, REGION_DRAW_POST_PIXEL); + + return true; +} + +static void bonedropper_exit(bContext *C, wmOperator *op) +{ + wmWindow *win = CTX_wm_window(C); + WM_cursor_modal_restore(win); + + if (op->customdata) { + BoneDropper *bdr = (BoneDropper *)op->customdata; + + if (bdr->area_region_type) { + ED_region_draw_cb_exit(bdr->area_region_type, bdr->draw_handle_pixel); + } + ED_area_tag_redraw(bdr->cursor_area); + + MEM_freeN(op->customdata); + + op->customdata = nullptr; + } + WM_event_add_mousemove(win); +} + +static void bonedropper_cancel(bContext *C, wmOperator *op) +{ + bonedropper_exit(C, op); +} + +/* To switch the draw callback when region under mouse event changes */ +static void bonedropper_set_draw_callback_region(ScrArea &area, BoneDropper &bdr) +{ + if (area.spacetype == bdr.cursor_area->spacetype) { + return; + } + + /* If the spacetype changed remove the old callback. */ + ED_region_draw_cb_exit(bdr.area_region_type, bdr.draw_handle_pixel); + + ARegion *region = BKE_area_find_region_type(bdr.cursor_area, RGN_TYPE_WINDOW); + ED_region_tag_redraw(region); + + /* Set draw callback in new region. */ + ARegionType *art = BKE_regiontype_from_id(area.type, RGN_TYPE_WINDOW); + + bdr.cursor_area = &area; + bdr.area_region_type = art; + bdr.draw_handle_pixel = ED_region_draw_cb_activate( + art, datadropper_draw_cb, &bdr, REGION_DRAW_POST_PIXEL); +} + +static BoneSampleData sample_data_from_3d_view(bContext *C, + const int mval[2], + const BoneDropper &bdr) +{ + Base *base = nullptr; + + switch (CTX_data_mode_enum(C)) { + case CTX_MODE_POSE: { + bPoseChannel *bone = ED_armature_pick_pchan(C, mval, true, &base); + if (!bone || !base) { + return {SampleResult::NO_BONE_3DVIEW}; + } + Object *ob = base->object; + bArmature *armature = (bArmature *)ob->data; + if (!armature || &armature->id != bdr.search_ptr.owner_id) { + return {SampleResult::WRONG_ARMATURE}; + } + + BoneSampleData sample_data; + sample_data.name = bone->name; + /* Not using the search pointer owner ID because pose bones are part of the object. */ + sample_data.bone_rna = RNA_pointer_create(&base->object->id, &RNA_PoseBone, bone); + sample_data.sample_result = SampleResult::SUCCESS; + return sample_data; + } + + case CTX_MODE_EDIT_ARMATURE: { + EditBone *ebone = ED_armature_pick_ebone(C, mval, true, &base); + if (!ebone || !base) { + return {SampleResult::NO_BONE_3DVIEW}; + } + Object *ob = base->object; + bArmature *armature = (bArmature *)ob->data; + if (!armature || &armature->id != bdr.search_ptr.owner_id) { + return {SampleResult::WRONG_ARMATURE}; + } + + BoneSampleData sample_data; + sample_data.name = ebone->name; + sample_data.bone_rna = RNA_pointer_create(&armature->id, &RNA_EditBone, ebone); + sample_data.sample_result = SampleResult::SUCCESS; + return sample_data; + } + + default: + return {SampleResult::NO_BONE_3DVIEW}; + } +} + +static BoneSampleData sample_data_from_outliner(bContext *C, + const int mval[2], + const BoneDropper &bdr) +{ + BoneSampleData sample_data; + + const bool success = ED_outliner_give_rna_under_cursor(C, mval, &sample_data.bone_rna); + if (!success) { + sample_data.sample_result = SampleResult::NO_BONE_OUTLINER; + return sample_data; + } + ID *bone_id = sample_data.bone_rna.owner_id; + ID *search_id = bdr.search_ptr.owner_id; + + /* By comparing the ID of the RNA returned by the outliner with the ID we are searching in, we + * can determine if the Bone is for the correct armature. */ + if (sample_data.bone_rna.type == &RNA_Bone) { + if (bone_id != search_id) { + sample_data.sample_result = SampleResult::WRONG_ARMATURE; + return sample_data; + } + Bone *bone = (Bone *)sample_data.bone_rna.data; + sample_data.name = bone->name; + sample_data.sample_result = SampleResult::SUCCESS; + return sample_data; + } + + if (sample_data.bone_rna.type == &RNA_EditBone) { + if (bone_id != search_id) { + sample_data.sample_result = SampleResult::WRONG_ARMATURE; + return sample_data; + } + EditBone *bone = (EditBone *)sample_data.bone_rna.data; + sample_data.name = bone->name; + sample_data.sample_result = SampleResult::SUCCESS; + return sample_data; + } + + if (sample_data.bone_rna.type == &RNA_PoseBone) { + bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data; + /* Special case for pose bones. Because they are not stored in the Armature, the IDs of the + * search property and the picked result might not match since the comparison would be between + * armature and object. */ + if (bdr.search_ptr.type == &RNA_Object) { + if (bone_id != search_id) { + sample_data.sample_result = SampleResult::WRONG_ARMATURE; + return sample_data; + } + } + /* If looking for an armature, get the Armature object and follow the data pointer. */ + if (bdr.search_ptr.type == &RNA_Armature) { + /* Expecting Pose Bones to be stored on the object. */ + BLI_assert(GS(sample_data.bone_rna.owner_id->name) == ID_OB); + Object *armature_object = (Object *)sample_data.bone_rna.owner_id; + if (armature_object->data != bdr.search_ptr.owner_id) { + sample_data.sample_result = SampleResult::WRONG_ARMATURE; + return sample_data; + } + } + sample_data.name = pose_bone->name; + sample_data.sample_result = SampleResult::SUCCESS; + return sample_data; + } + + sample_data.sample_result = SampleResult::NO_BONE_OUTLINER; + return sample_data; +} + +static BoneSampleData bonedropper_sample_pt( + bContext *C, wmWindow &win, ScrArea &area, BoneDropper &bdr, const int event_xy[2]) +{ + if (!ELEM(area.spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) { + return {}; + } + + ARegion *region = BKE_area_find_region_xy(&area, RGN_TYPE_WINDOW, event_xy); + + if (!region) { + return {}; + } + + wmWindow *win_prev = CTX_wm_window(C); + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + const int mval[2] = {event_xy[0] - region->winrct.xmin, event_xy[1] - region->winrct.ymin}; + + CTX_wm_window_set(C, &win); + CTX_wm_area_set(C, &area); + CTX_wm_region_set(C, region); + + /* Unfortunately it's necessary to always draw else we leave stale text. */ + ED_region_tag_redraw(region); + + BoneSampleData sample_data; + switch (area.spacetype) { + case SPACE_VIEW3D: { + sample_data = sample_data_from_3d_view(C, mval, bdr); + break; + } + case SPACE_OUTLINER: { + sample_data = sample_data_from_outliner(C, mval, bdr); + break; + } + + default: + BLI_assert_unreachable(); + break; + } + + if (sample_data.name) { + SNPRINTF(bdr.name, "%s", sample_data.name); + copy_v2_v2_int(bdr.name_pos, mval); + } + + CTX_wm_window_set(C, win_prev); + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); + + return sample_data; +} + +static SampleResult bonedropper_sample(bContext *C, BoneDropper &bdr, const int event_xy[2]) +{ + int event_xy_win[2]; + wmWindow *win = nullptr; + ScrArea *area = nullptr; + eyedropper_win_area_find(C, event_xy, event_xy_win, &win, &area); + + if (!win || !area) { + return SampleResult::WRONG_AREA; + } + if (!ELEM(area->spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) { + return SampleResult::WRONG_AREA; + } + + BoneSampleData sample_data = bonedropper_sample_pt(C, *win, *area, bdr, event_xy_win); + if (!sample_data.name) { + return sample_data.sample_result; + } + + StructRNA *search_type = RNA_property_pointer_type(&bdr.search_ptr, bdr.search_prop); + /* In case we are searching for a bone, convert the pointer from bPoseChannel. */ + if (search_type == &RNA_Bone && sample_data.bone_rna.type == &RNA_PoseBone && + bdr.search_ptr.type == &RNA_Armature) + { + /* We are searching for something in the armature but got a pose bone on the object, so we + * need to do a conversion. We will just assume the ID under the cursor is the one we are + * searching for since there is no way to get the armature ID from the object ID that we + * have. */ + bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data; + sample_data.bone_rna = RNA_pointer_create(bdr.search_ptr.owner_id, &RNA_Bone, pose_bone->bone); + } + + PropertyType type = RNA_property_type(bdr.prop); + switch (type) { + case PROP_STRING: + RNA_property_string_set(&bdr.ptr, bdr.prop, sample_data.name); + break; + case PROP_POINTER: + RNA_property_pointer_set(&bdr.ptr, bdr.prop, sample_data.bone_rna, CTX_wm_reports(C)); + break; + + default: + BLI_assert_unreachable(); + break; + } + + RNA_property_update(C, &bdr.ptr, bdr.prop); + + return SampleResult::SUCCESS; +} + +static void generate_sample_warning(SampleResult result, wmOperator *op) +{ + switch (result) { + case SampleResult::UNKNOWN_FAILURE: + BKE_report(op->reports, RPT_WARNING, "Picking a bone failed"); + break; + case SampleResult::WRONG_ARMATURE: + BKE_report( + op->reports, RPT_WARNING, "Picked bone does not belong to the already chosen armature"); + break; + + case SampleResult::NO_BONE_3DVIEW: + BKE_report(op->reports, + RPT_WARNING, + "Selection is not a bone. Armature needs to be in Pose Mode or Edit Mode " + "to pick in the 3D Viewport"); + break; + + case SampleResult::NO_BONE_OUTLINER: + BKE_report(op->reports, RPT_WARNING, "Selection is not a bone"); + break; + + case SampleResult::WRONG_AREA: + BKE_report(op->reports, RPT_WARNING, "Can only pick from the 3D viewport or the outliner"); + break; + + case SampleResult::SUCCESS: + break; + } +} + +static int bonedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + BoneDropper *bdr = (BoneDropper *)op->customdata; + if (!bdr) { + return OPERATOR_CANCELLED; + } + + if (event->type == EVT_MODAL_MAP) { + switch (event->val) { + case EYE_MODAL_CANCEL: + bonedropper_cancel(C, op); + return OPERATOR_CANCELLED; + case EYE_MODAL_SAMPLE_CONFIRM: { + const bool is_undo = bdr->is_undo; + const SampleResult result = bonedropper_sample(C, *bdr, event->xy); + bonedropper_exit(C, op); + if (result == SampleResult::SUCCESS) { + /* Could support finished & undo-skip. */ + return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED; + } + generate_sample_warning(result, op); + } + } + } + else if (event->type == MOUSEMOVE) { + bdr->name[0] = '\0'; + int event_xy_win[2]; + wmWindow *win = nullptr; + ScrArea *area = nullptr; + eyedropper_win_area_find(C, event->xy, event_xy_win, &win, &area); + + if (win && area) { + /* Set the region for eyedropper cursor text drawing */ + bonedropper_set_draw_callback_region(*area, *bdr); + bonedropper_sample_pt(C, *win, *area, *bdr, event->xy); + } + } + + return OPERATOR_RUNNING_MODAL; +} +static int bonedropper_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) +{ + /* This is needed to ensure viewport picking works. */ + BKE_object_update_select_id(CTX_data_main(C)); + + if (bonedropper_init(C, op)) { + wmWindow *win = CTX_wm_window(C); + /* Workaround for de-activating the button clearing the cursor, see #76794 */ + UI_context_active_but_clear(C, win, CTX_wm_region(C)); + WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; + } + return OPERATOR_CANCELLED; +} + +static int bonedropper_exec(bContext *C, wmOperator *op) +{ + if (bonedropper_init(C, op)) { + bonedropper_exit(C, op); + + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool bonedropper_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index_dummy; + + if (CTX_wm_window(C) == nullptr) { + return false; + } + + uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy); + + if (!but) { + return false; + } + + if (but->type != UI_BTYPE_SEARCH_MENU || !(but->flag & UI_BUT_VALUE_CLEAR)) { + return false; + } + + uiButSearch *search_but = (uiButSearch *)but; + + if (!ELEM(RNA_property_type(prop), PROP_STRING, PROP_POINTER)) { + return false; + } + + StructRNA *type = RNA_property_pointer_type(&search_but->rnasearchpoin, + search_but->rnasearchprop); + + return type == &RNA_Bone || type == &RNA_EditBone; +} + +void UI_OT_eyedropper_bone(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Eyedropper Bone"; + ot->idname = "UI_OT_eyedropper_bone"; + ot->description = "Sample a bone from the 3D View or the Outliner to store in a property"; + + /* API callbacks. */ + ot->invoke = bonedropper_invoke; + ot->modal = bonedropper_modal; + ot->cancel = bonedropper_cancel; + ot->exec = bonedropper_exec; + ot->poll = bonedropper_poll; + + /* Flags. */ + ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL; +} + +} // namespace blender::ui diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_datablock.cc b/source/blender/editors/interface/eyedroppers/eyedropper_datablock.cc index 42eb8402849..b1926fb9106 100644 --- a/source/blender/editors/interface/eyedroppers/eyedropper_datablock.cc +++ b/source/blender/editors/interface/eyedroppers/eyedropper_datablock.cc @@ -219,7 +219,7 @@ static bool datadropper_id_sample(bContext *C, DataDropper *ddr, const int event int event_xy_win[2]; wmWindow *win; ScrArea *area; - datadropper_win_area_find(C, event_xy, event_xy_win, &win, &area); + eyedropper_win_area_find(C, event_xy, event_xy_win, &win, &area); datadropper_id_sample_pt(C, win, area, ddr, event_xy_win, &id); return datadropper_id_set(C, ddr, id); @@ -286,7 +286,7 @@ static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event) int event_xy_win[2]; wmWindow *win; ScrArea *area; - datadropper_win_area_find(C, event->xy, event_xy_win, &win, &area); + eyedropper_win_area_find(C, event->xy, event_xy_win, &win, &area); /* Set the region for eyedropper cursor text drawing */ datadropper_set_draw_callback_region(area, ddr); diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_intern.hh b/source/blender/editors/interface/eyedroppers/eyedropper_intern.hh index 691f513a741..3594f6a43a8 100644 --- a/source/blender/editors/interface/eyedroppers/eyedropper_intern.hh +++ b/source/blender/editors/interface/eyedroppers/eyedropper_intern.hh @@ -24,11 +24,11 @@ void eyedropper_draw_cursor_text_region(const int xy[2], const char *name); * \return A button under the mouse which relates to some RNA Property, or NULL */ uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event); -void datadropper_win_area_find(const bContext *C, - const int event_xy[2], - int r_event_xy[2], - wmWindow **r_win, - ScrArea **r_area); +void eyedropper_win_area_find(const bContext *C, + const int event_xy[2], + int r_event_xy[2], + wmWindow **r_win, + ScrArea **r_area); /* interface_eyedropper_color.c (expose for color-band picker) */ diff --git a/source/blender/editors/interface/eyedroppers/interface_eyedropper.cc b/source/blender/editors/interface/eyedroppers/interface_eyedropper.cc index 73c2b057485..30e17bb98d5 100644 --- a/source/blender/editors/interface/eyedroppers/interface_eyedropper.cc +++ b/source/blender/editors/interface/eyedroppers/interface_eyedropper.cc @@ -53,6 +53,7 @@ wmKeyMap *eyedropper_modal_keymap(wmKeyConfig *keyconf) WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_colorramp"); WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_color"); WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_id"); + WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_bone"); WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_depth"); WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_driver"); WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_gpencil_color"); @@ -141,11 +142,11 @@ uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *ev return but; } -void datadropper_win_area_find(const bContext *C, - const int event_xy[2], - int r_event_xy[2], - wmWindow **r_win, - ScrArea **r_area) +void eyedropper_win_area_find(const bContext *C, + const int event_xy[2], + int r_event_xy[2], + wmWindow **r_win, + ScrArea **r_area) { bScreen *screen = CTX_wm_screen(C); diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 87ec2693056..3830bd0afa4 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -1619,6 +1619,7 @@ enum PredefinedExtraOpIconType { PREDEFINED_EXTRA_OP_ICON_NONE = 1, PREDEFINED_EXTRA_OP_ICON_CLEAR, PREDEFINED_EXTRA_OP_ICON_EYEDROPPER, + PREDEFINED_EXTRA_OP_ICON_BONE_EYEDROPPER }; static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but, @@ -1710,6 +1711,23 @@ static bool ui_but_icon_extra_is_visible_search_eyedropper(uiBut *but) return ((but->editstr == nullptr) && (idcode == ID_OB || OB_DATA_SUPPORT_ID(idcode))); } +static bool ui_but_icon_extra_is_visible_bone_eyedropper(uiBut *but) +{ + if (but->type != UI_BTYPE_SEARCH_MENU || !(but->flag & UI_BUT_VALUE_CLEAR)) { + return false; + } + if (but->editstr != nullptr) { + return false; + } + if (!ELEM(RNA_property_type(but->rnaprop), PROP_STRING, PROP_POINTER)) { + return false; + } + uiButSearch *search_but = (uiButSearch *)but; + StructRNA *type = RNA_property_pointer_type(&search_but->rnasearchpoin, + search_but->rnasearchprop); + return type == &RNA_Bone || type == &RNA_EditBone; +} + static PredefinedExtraOpIconType ui_but_icon_extra_get(uiBut *but) { switch (but->type) { @@ -1728,6 +1746,9 @@ static PredefinedExtraOpIconType ui_but_icon_extra_get(uiBut *but) else if (ui_but_icon_extra_is_visible_search_eyedropper(but)) { return PREDEFINED_EXTRA_OP_ICON_EYEDROPPER; } + else if (ui_but_icon_extra_is_visible_bone_eyedropper(but)) { + return PREDEFINED_EXTRA_OP_ICON_BONE_EYEDROPPER; + } break; default: break; @@ -1760,6 +1781,17 @@ static void ui_but_predefined_extra_operator_icons_add(uiBut *but) break; } + case PREDEFINED_EXTRA_OP_ICON_BONE_EYEDROPPER: { + static wmOperatorType *id_eyedropper_ot = nullptr; + if (!id_eyedropper_ot) { + id_eyedropper_ot = WM_operatortype_find("UI_OT_eyedropper_bone", false); + } + BLI_assert(id_eyedropper_ot); + + optype = id_eyedropper_ot; + icon = ICON_EYEDROPPER; + break; + } case PREDEFINED_EXTRA_OP_ICON_CLEAR: { static wmOperatorType *clear_ot = nullptr; if (!clear_ot) { diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index 1fa77669b1c..2b704ca5a6b 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -1553,6 +1553,9 @@ void UI_OT_eyedropper_color(wmOperatorType *ot); namespace blender::ui { void UI_OT_eyedropper_colorramp(wmOperatorType *ot); void UI_OT_eyedropper_colorramp_point(wmOperatorType *ot); + +void UI_OT_eyedropper_bone(wmOperatorType *ot); + } // namespace blender::ui /* interface_eyedropper_datablock.c */ diff --git a/source/blender/editors/interface/interface_ops.cc b/source/blender/editors/interface/interface_ops.cc index 0ffa9c56191..0d986033330 100644 --- a/source/blender/editors/interface/interface_ops.cc +++ b/source/blender/editors/interface/interface_ops.cc @@ -2774,6 +2774,7 @@ void ED_operatortypes_ui() WM_operatortype_append(UI_OT_eyedropper_depth); WM_operatortype_append(UI_OT_eyedropper_driver); WM_operatortype_append(UI_OT_eyedropper_gpencil_color); + WM_operatortype_append(UI_OT_eyedropper_bone); WM_operatortype_append(UI_OT_eyedropper_grease_pencil_color); } diff --git a/source/blender/editors/space_outliner/outliner_utils.cc b/source/blender/editors/space_outliner/outliner_utils.cc index 78e58b639df..57783da47ff 100644 --- a/source/blender/editors/space_outliner/outliner_utils.cc +++ b/source/blender/editors/space_outliner/outliner_utils.cc @@ -16,6 +16,9 @@ #include "DNA_screen_types.h" #include "DNA_space_types.h" +#include "RNA_access.hh" +#include "RNA_prototypes.hh" + #include "BKE_context.hh" #include "BKE_layer.hh" #include "BKE_object.hh" @@ -478,3 +481,42 @@ Base *ED_outliner_give_base_under_cursor(bContext *C, const int mval[2]) return base; } + +bool ED_outliner_give_rna_under_cursor(bContext *C, const int mval[2], PointerRNA *r_ptr) +{ + ARegion *region = CTX_wm_region(C); + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + + float view_mval[2]; + UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &view_mval[0], &view_mval[1]); + + TreeElement *te = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]); + if (!te) { + return false; + } + + bool success = true; + TreeStoreElem *tselem = TREESTORE(te); + switch (tselem->type) { + case TSE_BONE: { + Bone *bone = (Bone *)te->directdata; + *r_ptr = RNA_pointer_create(tselem->id, &RNA_Bone, bone); + break; + } + case TSE_POSE_CHANNEL: { + bPoseChannel *pchan = (bPoseChannel *)te->directdata; + *r_ptr = RNA_pointer_create(tselem->id, &RNA_PoseBone, pchan); + break; + } + case TSE_EBONE: { + EditBone *bone = (EditBone *)te->directdata; + *r_ptr = RNA_pointer_create(tselem->id, &RNA_EditBone, bone); + break; + } + + default: + success = false; + break; + } + return success; +}