Blender V4.5
eyedropper_bone.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include "BKE_armature.hh"
12#include "BKE_context.hh"
13#include "BKE_object.hh"
14#include "BKE_report.hh"
15#include "BKE_screen.hh"
16
17#include "BLI_assert.h"
18#include "BLI_math_vector.h"
19#include "BLI_string.h"
20
21#include "RNA_access.hh"
22#include "RNA_prototypes.hh"
23
24#include "ED_armature.hh"
25#include "ED_outliner.hh"
26#include "ED_screen.hh"
27#include "ED_space_api.hh"
28#include "ED_view3d.hh"
29
30#include "WM_api.hh"
31#include "WM_types.hh"
32
33#include "UI_interface.hh"
34#include "UI_view2d.hh"
35
36#include "eyedropper_intern.hh"
37#include "interface_intern.hh"
38
39namespace blender::ui {
40
49
52 PropertyRNA *prop = nullptr;
55
56 bool is_undo = false;
57
58 ScrArea *cursor_area = nullptr; /* Area under the cursor. */
60 void *draw_handle_pixel = nullptr;
61 int name_pos[2] = {};
62 char name[64] = {};
63};
64
67 /* Either EditBone, bPoseChannel or Bone. */
69 char *name = nullptr;
70};
71
72static void datadropper_draw_cb(const bContext * /*C*/, ARegion * /*region*/, void *arg)
73{
74 BoneDropper *ddr = static_cast<BoneDropper *>(arg);
76}
77
78static bool is_bone_dropper_valid(BoneDropper *bone_dropper)
79{
80 if ((bone_dropper->ptr.data == nullptr) || (bone_dropper->prop == nullptr)) {
81 return false;
82 }
83 if (!RNA_property_editable(&bone_dropper->ptr, bone_dropper->prop)) {
84 return false;
85 }
86
87 PointerRNA owner_ptr = RNA_id_pointer_create(bone_dropper->search_ptr.owner_id);
88 if (RNA_type_to_ID_code(owner_ptr.type) != ID_AR) {
89 return false;
90 }
91
92 return true;
93}
94
96{
97 int index_dummy;
98 PointerRNA button_ptr;
99 PropertyRNA *button_prop;
100 uiBut *button = UI_context_active_but_prop_get(C, &button_ptr, &button_prop, &index_dummy);
101
102 if (!button || button->type != UI_BTYPE_SEARCH_MENU) {
103 return false;
104 }
105
106 BoneDropper *bone_dropper = MEM_new<BoneDropper>(__func__);
107 uiButSearch *search_button = (uiButSearch *)button;
108 bone_dropper->ptr = button_ptr;
109 bone_dropper->prop = button_prop;
110 bone_dropper->search_ptr = search_button->rnasearchpoin;
111 bone_dropper->search_prop = search_button->rnasearchprop;
112 if (!is_bone_dropper_valid(bone_dropper)) {
113 MEM_delete(bone_dropper);
114 return false;
115 }
116
117 op->customdata = bone_dropper;
118
119 bone_dropper->is_undo = UI_but_flag_is_set(button, UI_BUT_UNDO);
120
122 ARegionType *area_region_type = BKE_regiontype_from_id(space_type, RGN_TYPE_WINDOW);
123 bone_dropper->cursor_area = CTX_wm_area(C);
124 bone_dropper->area_region_type = area_region_type;
126 area_region_type, datadropper_draw_cb, bone_dropper, REGION_DRAW_POST_PIXEL);
127
128 return true;
129}
130
132{
133 wmWindow *win = CTX_wm_window(C);
135
136 if (op->customdata) {
137 BoneDropper *bdr = (BoneDropper *)op->customdata;
138 op->customdata = nullptr;
139
140 if (bdr->area_region_type) {
142 }
144
145 MEM_delete(bdr);
146 }
148}
149
151{
152 bonedropper_exit(C, op);
153}
154
155/* To switch the draw callback when region under mouse event changes */
157{
158 if (area.spacetype == bdr.cursor_area->spacetype) {
159 return;
160 }
161
162 /* If the spacetype changed remove the old callback. */
164
166 ED_region_tag_redraw(region);
167
168 /* Set draw callback in new region. */
170
171 bdr.cursor_area = &area;
172 bdr.area_region_type = art;
175}
176
178 const int mval[2],
179 const BoneDropper &bdr)
180{
181 Base *base = nullptr;
182
183 switch (CTX_data_mode_enum(C)) {
184 case CTX_MODE_POSE: {
185 bPoseChannel *bone = ED_armature_pick_pchan(C, mval, true, &base);
186 if (!bone || !base) {
188 }
189 Object *ob = base->object;
190 bArmature *armature = (bArmature *)ob->data;
191 if (!armature || &armature->id != bdr.search_ptr.owner_id) {
193 }
194
195 BoneSampleData sample_data;
196 sample_data.name = bone->name;
197 /* Not using the search pointer owner ID because pose bones are part of the object. */
198 sample_data.bone_rna = RNA_pointer_create_discrete(&base->object->id, &RNA_PoseBone, bone);
200 return sample_data;
201 }
202
204 EditBone *ebone = ED_armature_pick_ebone(C, mval, true, &base);
205 if (!ebone || !base) {
207 }
208 Object *ob = base->object;
209 bArmature *armature = (bArmature *)ob->data;
210 if (!armature || &armature->id != bdr.search_ptr.owner_id) {
212 }
213
214 BoneSampleData sample_data;
215 sample_data.name = ebone->name;
216 sample_data.bone_rna = RNA_pointer_create_discrete(&armature->id, &RNA_EditBone, ebone);
218 return sample_data;
219 }
220
221 default:
223 }
224}
225
227 const int mval[2],
228 const BoneDropper &bdr)
229{
230 BoneSampleData sample_data;
231
232 const bool success = ED_outliner_give_rna_under_cursor(C, mval, &sample_data.bone_rna);
233 if (!success) {
235 return sample_data;
236 }
237 ID *bone_id = sample_data.bone_rna.owner_id;
238 ID *search_id = bdr.search_ptr.owner_id;
239
240 /* By comparing the ID of the RNA returned by the outliner with the ID we are searching in, we
241 * can determine if the Bone is for the correct armature. */
242 if (sample_data.bone_rna.type == &RNA_Bone) {
243 if (bone_id != search_id) {
245 return sample_data;
246 }
247 Bone *bone = (Bone *)sample_data.bone_rna.data;
248 sample_data.name = bone->name;
250 return sample_data;
251 }
252
253 if (sample_data.bone_rna.type == &RNA_EditBone) {
254 if (bone_id != search_id) {
256 return sample_data;
257 }
258 EditBone *bone = (EditBone *)sample_data.bone_rna.data;
259 sample_data.name = bone->name;
261 return sample_data;
262 }
263
264 if (sample_data.bone_rna.type == &RNA_PoseBone) {
265 bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data;
266 /* Special case for pose bones. Because they are not stored in the Armature, the IDs of the
267 * search property and the picked result might not match since the comparison would be between
268 * armature and object. */
269 if (bdr.search_ptr.type == &RNA_Object) {
270 if (bone_id != search_id) {
272 return sample_data;
273 }
274 }
275 /* If looking for an armature, get the Armature object and follow the data pointer. */
276 if (bdr.search_ptr.type == &RNA_Armature) {
277 /* Expecting Pose Bones to be stored on the object. */
278 BLI_assert(GS(sample_data.bone_rna.owner_id->name) == ID_OB);
279 Object *armature_object = (Object *)sample_data.bone_rna.owner_id;
280 if (armature_object->data != bdr.search_ptr.owner_id) {
282 return sample_data;
283 }
284 }
285 sample_data.name = pose_bone->name;
287 return sample_data;
288 }
289
291 return sample_data;
292}
293
295 bContext *C, wmWindow &win, ScrArea &area, BoneDropper &bdr, const int event_xy[2])
296{
298 return {};
299 }
300
301 ARegion *region = BKE_area_find_region_xy(&area, RGN_TYPE_WINDOW, event_xy);
302
303 if (!region) {
304 return {};
305 }
306
307 wmWindow *win_prev = CTX_wm_window(C);
308 ScrArea *area_prev = CTX_wm_area(C);
309 ARegion *region_prev = CTX_wm_region(C);
310
311 const int mval[2] = {event_xy[0] - region->winrct.xmin, event_xy[1] - region->winrct.ymin};
312
313 CTX_wm_window_set(C, &win);
314 CTX_wm_area_set(C, &area);
315 CTX_wm_region_set(C, region);
316
317 /* Unfortunately it's necessary to always draw else we leave stale text. */
318 ED_region_tag_redraw(region);
319
320 BoneSampleData sample_data;
321 switch (area.spacetype) {
322 case SPACE_VIEW3D: {
323 sample_data = sample_data_from_3d_view(C, mval, bdr);
324 break;
325 }
326 case SPACE_OUTLINER: {
327 sample_data = sample_data_from_outliner(C, mval, bdr);
328 break;
329 }
330
331 default:
333 break;
334 }
335
336 if (sample_data.name) {
337 SNPRINTF(bdr.name, "%s", sample_data.name);
338 copy_v2_v2_int(bdr.name_pos, mval);
339 }
340
341 CTX_wm_window_set(C, win_prev);
342 CTX_wm_area_set(C, area_prev);
343 CTX_wm_region_set(C, region_prev);
344
345 return sample_data;
346}
347
348static SampleResult bonedropper_sample(bContext *C, BoneDropper &bdr, const int event_xy[2])
349{
350 int event_xy_win[2];
351 wmWindow *win = nullptr;
352 ScrArea *area = nullptr;
353 eyedropper_win_area_find(C, event_xy, event_xy_win, &win, &area);
354
355 if (!win || !area) {
357 }
360 }
361
362 BoneSampleData sample_data = bonedropper_sample_pt(C, *win, *area, bdr, event_xy_win);
363 if (!sample_data.name) {
364 return sample_data.sample_result;
365 }
366
368 /* In case we are searching for a bone, convert the pointer from bPoseChannel. */
369 if (search_type == &RNA_Bone && sample_data.bone_rna.type == &RNA_PoseBone &&
370 bdr.search_ptr.type == &RNA_Armature)
371 {
372 /* We are searching for something in the armature but got a pose bone on the object, so we
373 * need to do a conversion. We will just assume the ID under the cursor is the one we are
374 * searching for since there is no way to get the armature ID from the object ID that we
375 * have. */
376 bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data;
378 bdr.search_ptr.owner_id, &RNA_Bone, pose_bone->bone);
379 }
380
382 switch (type) {
383 case PROP_STRING:
384 RNA_property_string_set(&bdr.ptr, bdr.prop, sample_data.name);
385 break;
386 case PROP_POINTER:
387 RNA_property_pointer_set(&bdr.ptr, bdr.prop, sample_data.bone_rna, CTX_wm_reports(C));
388 break;
389
390 default:
392 break;
393 }
394
395 RNA_property_update(C, &bdr.ptr, bdr.prop);
396
398}
399
401{
402 switch (result) {
404 BKE_report(op->reports, RPT_WARNING, "Picking a bone failed");
405 break;
408 op->reports, RPT_WARNING, "Picked bone does not belong to the already chosen armature");
409 break;
410
414 "Selection is not a bone. Armature needs to be in Pose Mode or Edit Mode "
415 "to pick in the 3D Viewport");
416 break;
417
419 BKE_report(op->reports, RPT_WARNING, "Selection is not a bone");
420 break;
421
423 BKE_report(op->reports, RPT_WARNING, "Can only pick from the 3D viewport or the outliner");
424 break;
425
427 break;
428 }
429}
430
432{
433 BoneDropper *bdr = (BoneDropper *)op->customdata;
434 if (!bdr) {
435 return OPERATOR_CANCELLED;
436 }
437
438 if (event->type == EVT_MODAL_MAP) {
439 switch (event->val) {
440 case EYE_MODAL_CANCEL:
442 return OPERATOR_CANCELLED;
444 const bool is_undo = bdr->is_undo;
445 const SampleResult result = bonedropper_sample(C, *bdr, event->xy);
446 bonedropper_exit(C, op);
448 /* Could support finished & undo-skip. */
449 return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
450 }
452 }
453 }
454 }
455 else if (event->type == MOUSEMOVE) {
456 bdr->name[0] = '\0';
457 int event_xy_win[2];
458 wmWindow *win = nullptr;
459 ScrArea *area = nullptr;
460 eyedropper_win_area_find(C, event->xy, event_xy_win, &win, &area);
461
462 if (win && area) {
463 /* Set the region for eyedropper cursor text drawing */
465 bonedropper_sample_pt(C, *win, *area, *bdr, event->xy);
466 }
467 }
468
470}
472{
473 /* This is needed to ensure viewport picking works. */
475
476 if (bonedropper_init(C, op)) {
477 wmWindow *win = CTX_wm_window(C);
478 /* Workaround for de-activating the button clearing the cursor, see #76794 */
481
484 }
485 return OPERATOR_CANCELLED;
486}
487
489{
490 if (bonedropper_init(C, op)) {
491 bonedropper_exit(C, op);
492
493 return OPERATOR_FINISHED;
494 }
495 return OPERATOR_CANCELLED;
496}
497
499{
501 PropertyRNA *prop;
502 int index_dummy;
503
504 if (CTX_wm_window(C) == nullptr) {
505 return false;
506 }
507
508 const Object *active_object = CTX_data_active_object(C);
509
510 if (!active_object || active_object->type != OB_ARMATURE) {
511 CTX_wm_operator_poll_msg_set(C, "The active object needs to be an armature");
512 return false;
513 }
514
515 if (!ELEM(active_object->mode, OB_MODE_POSE, OB_MODE_EDIT)) {
516 CTX_wm_operator_poll_msg_set(C, "The armature needs to be in Pose mode or Edit mode");
517 return false;
518 }
519
520 uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy);
521
522 if (!but) {
523 return false;
524 }
525
526 if (but->type != UI_BTYPE_SEARCH_MENU || !(but->flag & UI_BUT_VALUE_CLEAR)) {
527 return false;
528 }
529
530 uiButSearch *search_but = (uiButSearch *)but;
531
533 return false;
534 }
535
536 const StructRNA *type = RNA_property_pointer_type(&search_but->rnasearchpoin,
537 search_but->rnasearchprop);
538
539 return type == &RNA_Bone || type == &RNA_EditBone;
540}
541
543{
544 /* Identifiers. */
545 ot->name = "Eyedropper Bone";
546 ot->idname = "UI_OT_eyedropper_bone";
547 ot->description = "Sample a bone from the 3D View or the Outliner to store in a property";
548
549 /* API callbacks. */
550 ot->invoke = bonedropper_invoke;
551 ot->modal = bonedropper_modal;
552 ot->cancel = bonedropper_cancel;
553 ot->exec = bonedropper_exec;
554 ot->poll = bonedropper_poll;
555
556 /* Flags. */
558}
559
560} // namespace blender::ui
ReportList * CTX_wm_reports(const bContext *C)
@ CTX_MODE_EDIT_ARMATURE
@ CTX_MODE_POSE
void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
void CTX_wm_window_set(bContext *C, wmWindow *win)
Main * CTX_data_main(const bContext *C)
void CTX_wm_area_set(bContext *C, ScrArea *area)
void CTX_wm_region_set(bContext *C, ARegion *region)
ARegion * CTX_wm_region(const bContext *C)
enum eContextObjectMode CTX_data_mode_enum(const bContext *C)
General operations, lookup, etc. for blender objects.
void BKE_object_update_select_id(Main *bmain)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
ARegion * BKE_area_find_region_xy(const ScrArea *area, int regiontype, const int xy[2]) ATTR_NONNULL(3)
Definition screen.cc:869
SpaceType * BKE_spacetype_from_id(int spaceid)
Definition screen.cc:251
ARegionType * BKE_regiontype_from_id(const SpaceType *st, int regionid)
Definition screen.cc:261
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:840
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
#define ELEM(...)
@ ID_AR
@ ID_OB
@ OB_MODE_EDIT
@ OB_MODE_POSE
@ OB_ARMATURE
@ RGN_TYPE_WINDOW
@ SPACE_OUTLINER
@ SPACE_VIEW3D
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
bool ED_outliner_give_rna_under_cursor(bContext *C, const int mval[2], PointerRNA *r_ptr)
void ED_area_tag_redraw(ScrArea *area)
Definition area.cc:714
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
void * ED_region_draw_cb_activate(ARegionType *art, void(*draw)(const bContext *, ARegion *, void *), void *customdata, int type)
bool ED_region_draw_cb_exit(ARegionType *art, void *handle)
#define REGION_DRAW_POST_PIXEL
short RNA_type_to_ID_code(const StructRNA *type)
PropertyType
Definition RNA_types.hh:149
@ PROP_STRING
Definition RNA_types.hh:153
@ PROP_POINTER
Definition RNA_types.hh:155
#define C
Definition RandGen.cpp:29
uiBut * UI_context_active_but_prop_get(const bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
void UI_context_active_but_clear(bContext *C, wmWindow *win, ARegion *region)
@ UI_BTYPE_SEARCH_MENU
@ UI_BUT_UNDO
@ UI_BUT_VALUE_CLEAR
bool UI_but_flag_is_set(uiBut *but, int flag)
@ OPTYPE_INTERNAL
Definition WM_types.hh:202
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
@ OPTYPE_UNDO
Definition WM_types.hh:182
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)
void eyedropper_win_area_find(const bContext *C, const int event_xy[2], int r_event_xy[2], wmWindow **r_win, ScrArea **r_area)
void eyedropper_draw_cursor_text_region(const int xy[2], const char *name)
@ EYE_MODAL_CANCEL
@ EYE_MODAL_SAMPLE_CONFIRM
#define GS(a)
static void bonedropper_cancel(bContext *C, wmOperator *op)
static BoneSampleData sample_data_from_outliner(bContext *C, const int mval[2], const BoneDropper &bdr)
static wmOperatorStatus bonedropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
static wmOperatorStatus bonedropper_exec(bContext *C, wmOperator *op)
static void bonedropper_set_draw_callback_region(ScrArea &area, BoneDropper &bdr)
static void datadropper_draw_cb(const bContext *, ARegion *, void *arg)
static BoneSampleData bonedropper_sample_pt(bContext *C, wmWindow &win, ScrArea &area, BoneDropper &bdr, const int event_xy[2])
static wmOperatorStatus bonedropper_invoke(bContext *C, wmOperator *op, const wmEvent *)
static SampleResult bonedropper_sample(bContext *C, BoneDropper &bdr, const int event_xy[2])
static bool bonedropper_poll(bContext *C)
static int bonedropper_init(bContext *C, wmOperator *op)
void UI_OT_eyedropper_bone(wmOperatorType *ot)
static bool is_bone_dropper_valid(BoneDropper *bone_dropper)
static void generate_sample_warning(SampleResult result, wmOperator *op)
static BoneSampleData sample_data_from_3d_view(bContext *C, const int mval[2], const BoneDropper &bdr)
static void bonedropper_exit(bContext *C, wmOperator *op)
StructRNA * RNA_property_pointer_type(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_pointer_set(PointerRNA *ptr, PropertyRNA *prop, PointerRNA ptr_value, ReportList *reports)
PropertyType RNA_property_type(PropertyRNA *prop)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
bool RNA_property_editable(const PointerRNA *ptr, PropertyRNA *prop)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
PointerRNA RNA_id_pointer_create(ID *id)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
struct Object * object
char name[64]
char name[64]
Definition DNA_ID.h:404
char name[66]
Definition DNA_ID.h:415
ID * owner_id
Definition RNA_types.hh:51
StructRNA * type
Definition RNA_types.hh:52
void * data
Definition RNA_types.hh:53
struct SpaceType * type
struct Bone * bone
int ymin
int xmin
PropertyRNA * rnasearchprop
PointerRNA rnasearchpoin
eButType type
wmEventType type
Definition WM_types.hh:754
short val
Definition WM_types.hh:756
int xy[2]
Definition WM_types.hh:758
struct ReportList * reports
void WM_cursor_modal_set(wmWindow *win, int val)
void WM_cursor_modal_restore(wmWindow *win)
@ WM_CURSOR_EYEDROPPER
Definition wm_cursors.hh:36
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_mousemove(wmWindow *win)
@ EVT_MODAL_MAP
@ MOUSEMOVE
PointerRNA * ptr
Definition wm_files.cc:4226
wmOperatorType * ot
Definition wm_files.cc:4225