Blender V4.3
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
11#include "BKE_armature.hh"
12#include "BKE_context.hh"
13#include "BKE_idtype.hh"
14#include "BKE_object.hh"
15#include "BKE_report.hh"
16#include "BKE_screen.hh"
17
18#include "BLT_translation.hh"
19
20#include "BLI_assert.h"
21#include "BLI_math_vector.h"
22#include "BLI_string.h"
23
24#include "RNA_access.hh"
25#include "RNA_prototypes.hh"
26
27#include "ED_armature.hh"
28#include "ED_outliner.hh"
29#include "ED_screen.hh"
30#include "ED_space_api.hh"
31#include "ED_view3d.hh"
32
33#include "WM_api.hh"
34#include "WM_types.hh"
35
36#include "UI_interface.hh"
37#include "UI_view2d.hh"
38
39#include "eyedropper_intern.hh"
40#include "interface_intern.hh"
41
42namespace blender::ui {
43
52
67
70 /* Either EditBone, bPoseChannel or Bone. */
72 char *name = nullptr;
73};
74
75static void datadropper_draw_cb(const bContext * /*C*/, ARegion * /*region*/, void *arg)
76{
77 BoneDropper *ddr = static_cast<BoneDropper *>(arg);
79}
80
81static bool is_bone_dropper_valid(BoneDropper *bone_dropper)
82{
83 if ((bone_dropper->ptr.data == nullptr) || (bone_dropper->prop == nullptr)) {
84 return false;
85 }
86 if (!RNA_property_editable(&bone_dropper->ptr, bone_dropper->prop)) {
87 return false;
88 }
89
90 PointerRNA owner_ptr = RNA_id_pointer_create(bone_dropper->search_ptr.owner_id);
91 if (RNA_type_to_ID_code(owner_ptr.type) != ID_AR) {
92 return false;
93 }
94
95 return true;
96}
97
99{
100 int index_dummy;
101 PointerRNA button_ptr;
102 PropertyRNA *button_prop;
103 uiBut *button = UI_context_active_but_prop_get(C, &button_ptr, &button_prop, &index_dummy);
104
105 if (!button || button->type != UI_BTYPE_SEARCH_MENU) {
106 return false;
107 }
108
109 BoneDropper *bone_dropper = MEM_new<BoneDropper>(__func__);
110 uiButSearch *search_button = (uiButSearch *)button;
111 bone_dropper->ptr = button_ptr;
112 bone_dropper->prop = button_prop;
113 bone_dropper->search_ptr = search_button->rnasearchpoin;
114 bone_dropper->search_prop = search_button->rnasearchprop;
115 if (!is_bone_dropper_valid(bone_dropper)) {
116 MEM_delete(bone_dropper);
117 return false;
118 }
119
120 op->customdata = bone_dropper;
121
122 bone_dropper->is_undo = UI_but_flag_is_set(button, UI_BUT_UNDO);
123
125 ARegionType *area_region_type = BKE_regiontype_from_id(space_type, RGN_TYPE_WINDOW);
126 bone_dropper->cursor_area = CTX_wm_area(C);
127 bone_dropper->area_region_type = area_region_type;
129 area_region_type, datadropper_draw_cb, bone_dropper, REGION_DRAW_POST_PIXEL);
130
131 return true;
132}
133
135{
136 wmWindow *win = CTX_wm_window(C);
138
139 if (op->customdata) {
140 BoneDropper *bdr = (BoneDropper *)op->customdata;
141 op->customdata = nullptr;
142
143 if (bdr->area_region_type) {
145 }
147
148 MEM_delete(bdr);
149 }
151}
152
154{
155 bonedropper_exit(C, op);
156}
157
158/* To switch the draw callback when region under mouse event changes */
160{
161 if (area.spacetype == bdr.cursor_area->spacetype) {
162 return;
163 }
164
165 /* If the spacetype changed remove the old callback. */
167
169 ED_region_tag_redraw(region);
170
171 /* Set draw callback in new region. */
173
174 bdr.cursor_area = &area;
175 bdr.area_region_type = art;
178}
179
181 const int mval[2],
182 const BoneDropper &bdr)
183{
184 Base *base = nullptr;
185
186 switch (CTX_data_mode_enum(C)) {
187 case CTX_MODE_POSE: {
188 bPoseChannel *bone = ED_armature_pick_pchan(C, mval, true, &base);
189 if (!bone || !base) {
191 }
192 Object *ob = base->object;
193 bArmature *armature = (bArmature *)ob->data;
194 if (!armature || &armature->id != bdr.search_ptr.owner_id) {
196 }
197
198 BoneSampleData sample_data;
199 sample_data.name = bone->name;
200 /* Not using the search pointer owner ID because pose bones are part of the object. */
201 sample_data.bone_rna = RNA_pointer_create(&base->object->id, &RNA_PoseBone, bone);
203 return sample_data;
204 }
205
207 EditBone *ebone = ED_armature_pick_ebone(C, mval, true, &base);
208 if (!ebone || !base) {
210 }
211 Object *ob = base->object;
212 bArmature *armature = (bArmature *)ob->data;
213 if (!armature || &armature->id != bdr.search_ptr.owner_id) {
215 }
216
217 BoneSampleData sample_data;
218 sample_data.name = ebone->name;
219 sample_data.bone_rna = RNA_pointer_create(&armature->id, &RNA_EditBone, ebone);
221 return sample_data;
222 }
223
224 default:
226 }
227}
228
230 const int mval[2],
231 const BoneDropper &bdr)
232{
233 BoneSampleData sample_data;
234
235 const bool success = ED_outliner_give_rna_under_cursor(C, mval, &sample_data.bone_rna);
236 if (!success) {
238 return sample_data;
239 }
240 ID *bone_id = sample_data.bone_rna.owner_id;
241 ID *search_id = bdr.search_ptr.owner_id;
242
243 /* By comparing the ID of the RNA returned by the outliner with the ID we are searching in, we
244 * can determine if the Bone is for the correct armature. */
245 if (sample_data.bone_rna.type == &RNA_Bone) {
246 if (bone_id != search_id) {
248 return sample_data;
249 }
250 Bone *bone = (Bone *)sample_data.bone_rna.data;
251 sample_data.name = bone->name;
253 return sample_data;
254 }
255
256 if (sample_data.bone_rna.type == &RNA_EditBone) {
257 if (bone_id != search_id) {
259 return sample_data;
260 }
261 EditBone *bone = (EditBone *)sample_data.bone_rna.data;
262 sample_data.name = bone->name;
264 return sample_data;
265 }
266
267 if (sample_data.bone_rna.type == &RNA_PoseBone) {
268 bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data;
269 /* Special case for pose bones. Because they are not stored in the Armature, the IDs of the
270 * search property and the picked result might not match since the comparison would be between
271 * armature and object. */
272 if (bdr.search_ptr.type == &RNA_Object) {
273 if (bone_id != search_id) {
275 return sample_data;
276 }
277 }
278 /* If looking for an armature, get the Armature object and follow the data pointer. */
279 if (bdr.search_ptr.type == &RNA_Armature) {
280 /* Expecting Pose Bones to be stored on the object. */
281 BLI_assert(GS(sample_data.bone_rna.owner_id->name) == ID_OB);
282 Object *armature_object = (Object *)sample_data.bone_rna.owner_id;
283 if (armature_object->data != bdr.search_ptr.owner_id) {
285 return sample_data;
286 }
287 }
288 sample_data.name = pose_bone->name;
290 return sample_data;
291 }
292
294 return sample_data;
295}
296
298 bContext *C, wmWindow &win, ScrArea &area, BoneDropper &bdr, const int event_xy[2])
299{
300 if (!ELEM(area.spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) {
301 return {};
302 }
303
304 ARegion *region = BKE_area_find_region_xy(&area, RGN_TYPE_WINDOW, event_xy);
305
306 if (!region) {
307 return {};
308 }
309
310 wmWindow *win_prev = CTX_wm_window(C);
311 ScrArea *area_prev = CTX_wm_area(C);
312 ARegion *region_prev = CTX_wm_region(C);
313
314 const int mval[2] = {event_xy[0] - region->winrct.xmin, event_xy[1] - region->winrct.ymin};
315
316 CTX_wm_window_set(C, &win);
317 CTX_wm_area_set(C, &area);
318 CTX_wm_region_set(C, region);
319
320 /* Unfortunately it's necessary to always draw else we leave stale text. */
321 ED_region_tag_redraw(region);
322
323 BoneSampleData sample_data;
324 switch (area.spacetype) {
325 case SPACE_VIEW3D: {
326 sample_data = sample_data_from_3d_view(C, mval, bdr);
327 break;
328 }
329 case SPACE_OUTLINER: {
330 sample_data = sample_data_from_outliner(C, mval, bdr);
331 break;
332 }
333
334 default:
336 break;
337 }
338
339 if (sample_data.name) {
340 SNPRINTF(bdr.name, "%s", sample_data.name);
341 copy_v2_v2_int(bdr.name_pos, mval);
342 }
343
344 CTX_wm_window_set(C, win_prev);
345 CTX_wm_area_set(C, area_prev);
346 CTX_wm_region_set(C, region_prev);
347
348 return sample_data;
349}
350
351static SampleResult bonedropper_sample(bContext *C, BoneDropper &bdr, const int event_xy[2])
352{
353 int event_xy_win[2];
354 wmWindow *win = nullptr;
355 ScrArea *area = nullptr;
356 eyedropper_win_area_find(C, event_xy, event_xy_win, &win, &area);
357
358 if (!win || !area) {
360 }
361 if (!ELEM(area->spacetype, SPACE_VIEW3D, SPACE_OUTLINER)) {
363 }
364
365 BoneSampleData sample_data = bonedropper_sample_pt(C, *win, *area, bdr, event_xy_win);
366 if (!sample_data.name) {
367 return sample_data.sample_result;
368 }
369
371 /* In case we are searching for a bone, convert the pointer from bPoseChannel. */
372 if (search_type == &RNA_Bone && sample_data.bone_rna.type == &RNA_PoseBone &&
373 bdr.search_ptr.type == &RNA_Armature)
374 {
375 /* We are searching for something in the armature but got a pose bone on the object, so we
376 * need to do a conversion. We will just assume the ID under the cursor is the one we are
377 * searching for since there is no way to get the armature ID from the object ID that we
378 * have. */
379 bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data;
380 sample_data.bone_rna = RNA_pointer_create(bdr.search_ptr.owner_id, &RNA_Bone, pose_bone->bone);
381 }
382
384 switch (type) {
385 case PROP_STRING:
386 RNA_property_string_set(&bdr.ptr, bdr.prop, sample_data.name);
387 break;
388 case PROP_POINTER:
389 RNA_property_pointer_set(&bdr.ptr, bdr.prop, sample_data.bone_rna, CTX_wm_reports(C));
390 break;
391
392 default:
394 break;
395 }
396
397 RNA_property_update(C, &bdr.ptr, bdr.prop);
398
400}
401
403{
404 switch (result) {
406 BKE_report(op->reports, RPT_WARNING, "Picking a bone failed");
407 break;
410 op->reports, RPT_WARNING, "Picked bone does not belong to the already chosen armature");
411 break;
412
416 "Selection is not a bone. Armature needs to be in Pose Mode or Edit Mode "
417 "to pick in the 3D Viewport");
418 break;
419
421 BKE_report(op->reports, RPT_WARNING, "Selection is not a bone");
422 break;
423
425 BKE_report(op->reports, RPT_WARNING, "Can only pick from the 3D viewport or the outliner");
426 break;
427
429 break;
430 }
431}
432
433static int bonedropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
434{
435 BoneDropper *bdr = (BoneDropper *)op->customdata;
436 if (!bdr) {
437 return OPERATOR_CANCELLED;
438 }
439
440 if (event->type == EVT_MODAL_MAP) {
441 switch (event->val) {
442 case EYE_MODAL_CANCEL:
443 bonedropper_cancel(C, op);
444 return OPERATOR_CANCELLED;
446 const bool is_undo = bdr->is_undo;
447 const SampleResult result = bonedropper_sample(C, *bdr, event->xy);
448 bonedropper_exit(C, op);
449 if (result == SampleResult::SUCCESS) {
450 /* Could support finished & undo-skip. */
451 return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
452 }
453 generate_sample_warning(result, op);
454 }
455 }
456 }
457 else if (event->type == MOUSEMOVE) {
458 bdr->name[0] = '\0';
459 int event_xy_win[2];
460 wmWindow *win = nullptr;
461 ScrArea *area = nullptr;
462 eyedropper_win_area_find(C, event->xy, event_xy_win, &win, &area);
463
464 if (win && area) {
465 /* Set the region for eyedropper cursor text drawing */
467 bonedropper_sample_pt(C, *win, *area, *bdr, event->xy);
468 }
469 }
470
472}
473static int bonedropper_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
474{
475 /* This is needed to ensure viewport picking works. */
477
478 if (bonedropper_init(C, op)) {
479 wmWindow *win = CTX_wm_window(C);
480 /* Workaround for de-activating the button clearing the cursor, see #76794 */
483
486 }
487 return OPERATOR_CANCELLED;
488}
489
491{
492 if (bonedropper_init(C, op)) {
493 bonedropper_exit(C, op);
494
495 return OPERATOR_FINISHED;
496 }
497 return OPERATOR_CANCELLED;
498}
499
501{
503 PropertyRNA *prop;
504 int index_dummy;
505
506 if (CTX_wm_window(C) == nullptr) {
507 return false;
508 }
509
510 uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy);
511
512 if (!but) {
513 return false;
514 }
515
516 if (but->type != UI_BTYPE_SEARCH_MENU || !(but->flag & UI_BUT_VALUE_CLEAR)) {
517 return false;
518 }
519
520 uiButSearch *search_but = (uiButSearch *)but;
521
523 return false;
524 }
525
526 const StructRNA *type = RNA_property_pointer_type(&search_but->rnasearchpoin,
527 search_but->rnasearchprop);
528
529 return type == &RNA_Bone || type == &RNA_EditBone;
530}
531
533{
534 /* Identifiers. */
535 ot->name = "Eyedropper Bone";
536 ot->idname = "UI_OT_eyedropper_bone";
537 ot->description = "Sample a bone from the 3D View or the Outliner to store in a property";
538
539 /* API callbacks. */
545
546 /* Flags. */
548}
549
550} // namespace blender::ui
ReportList * CTX_wm_reports(const bContext *C)
@ CTX_MODE_EDIT_ARMATURE
@ CTX_MODE_POSE
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(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:125
ARegion * BKE_area_find_region_xy(const ScrArea *area, int regiontype, const int xy[2]) ATTR_NONNULL(3)
Definition screen.cc:844
SpaceType * BKE_spacetype_from_id(int spaceid)
Definition screen.cc:243
ARegionType * BKE_regiontype_from_id(const SpaceType *st, int regionid)
Definition screen.cc:253
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:815
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:597
#define ELEM(...)
@ ID_AR
@ ID_OB
@ RGN_TYPE_WINDOW
@ SPACE_OUTLINER
@ SPACE_VIEW3D
@ 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:708
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
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:64
@ PROP_STRING
Definition RNA_types.hh:68
@ PROP_POINTER
Definition RNA_types.hh:70
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
bool UI_but_flag_is_set(uiBut *but, int flag)
@ UI_BUT_UNDO
@ UI_BUT_VALUE_CLEAR
@ OPTYPE_INTERNAL
Definition WM_types.hh:182
@ OPTYPE_BLOCKING
Definition WM_types.hh:164
@ OPTYPE_UNDO
Definition WM_types.hh:162
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)
@ EYE_MODAL_CANCEL
@ EYE_MODAL_SAMPLE_CONFIRM
void eyedropper_draw_cursor_text_region(const int xy[2], const char *name)
#define GS(x)
Definition iris.cc:202
static int bonedropper_invoke(bContext *C, wmOperator *op, const wmEvent *)
static void bonedropper_cancel(bContext *C, wmOperator *op)
static int bonedropper_exec(bContext *C, wmOperator *op)
static BoneSampleData sample_data_from_outliner(bContext *C, const int mval[2], const BoneDropper &bdr)
static void bonedropper_set_draw_callback_region(ScrArea &area, BoneDropper &bdr)
static void datadropper_draw_cb(const bContext *, ARegion *, void *arg)
static int bonedropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
static BoneSampleData bonedropper_sample_pt(bContext *C, wmWindow &win, ScrArea &area, BoneDropper &bdr, const int event_xy[2])
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(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:413
char name[66]
Definition DNA_ID.h:425
ID * owner_id
Definition RNA_types.hh:40
StructRNA * type
Definition RNA_types.hh:41
void * data
Definition RNA_types.hh:42
struct Bone * bone
PropertyRNA * rnasearchprop
PointerRNA rnasearchpoin
eButType type
short val
Definition WM_types.hh:724
int xy[2]
Definition WM_types.hh:726
short type
Definition WM_types.hh:722
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1036
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1028
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:35
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:4126
wmOperatorType * ot
Definition wm_files.cc:4125