Blender V5.0
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_utf8.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_view2d.hh"
34
35#include "eyedropper_intern.hh"
36#include "interface_intern.hh"
37
38namespace blender::ui {
39
48
51 PropertyRNA *prop = nullptr;
54
55 bool is_undo = false;
56
57 ScrArea *cursor_area = nullptr; /* Area under the cursor. */
59 void *draw_handle_pixel = nullptr;
60 int name_pos[2] = {};
61 char name[64] = {};
62};
63
66 /* Either EditBone, bPoseChannel or Bone. */
68 char *name = nullptr;
69};
70
71static void datadropper_draw_cb(const bContext * /*C*/, ARegion * /*region*/, void *arg)
72{
73 BoneDropper *ddr = static_cast<BoneDropper *>(arg);
75}
76
77static bool is_bone_dropper_valid(BoneDropper *bone_dropper)
78{
79 if ((bone_dropper->ptr.data == nullptr) || (bone_dropper->prop == nullptr)) {
80 return false;
81 }
82 if (!RNA_property_editable(&bone_dropper->ptr, bone_dropper->prop)) {
83 return false;
84 }
85
86 PointerRNA owner_ptr = RNA_id_pointer_create(bone_dropper->search_ptr.owner_id);
87 if (RNA_type_to_ID_code(owner_ptr.type) != ID_AR) {
88 return false;
89 }
90
91 return true;
92}
93
95{
96 int index_dummy;
97 PointerRNA button_ptr;
98 PropertyRNA *button_prop;
99 uiBut *button = UI_context_active_but_prop_get(C, &button_ptr, &button_prop, &index_dummy);
100
101 if (!button || button->type != ButType::SearchMenu) {
102 return false;
103 }
104
105 BoneDropper *bone_dropper = MEM_new<BoneDropper>(__func__);
106 uiButSearch *search_button = (uiButSearch *)button;
107 bone_dropper->ptr = button_ptr;
108 bone_dropper->prop = button_prop;
109 bone_dropper->search_ptr = search_button->rnasearchpoin;
110 bone_dropper->search_prop = search_button->rnasearchprop;
111 if (!is_bone_dropper_valid(bone_dropper)) {
112 MEM_delete(bone_dropper);
113 return false;
114 }
115
116 op->customdata = bone_dropper;
117
118 bone_dropper->is_undo = UI_but_flag_is_set(button, UI_BUT_UNDO);
119
121 ARegionType *area_region_type = BKE_regiontype_from_id(space_type, RGN_TYPE_WINDOW);
122 bone_dropper->cursor_area = CTX_wm_area(C);
123 bone_dropper->area_region_type = area_region_type;
125 area_region_type, datadropper_draw_cb, bone_dropper, REGION_DRAW_POST_PIXEL);
126
127 return true;
128}
129
131{
132 wmWindow *win = CTX_wm_window(C);
134
135 if (op->customdata) {
136 BoneDropper *bdr = (BoneDropper *)op->customdata;
137 op->customdata = nullptr;
138
139 if (bdr->area_region_type) {
141 }
143
144 MEM_delete(bdr);
145 }
147}
148
150{
151 bonedropper_exit(C, op);
152}
153
154/* To switch the draw callback when region under mouse event changes */
156{
157 if (area.spacetype == bdr.cursor_area->spacetype) {
158 return;
159 }
160
161 /* If the spacetype changed remove the old callback. */
163
165 ED_region_tag_redraw(region);
166
167 /* Set draw callback in new region. */
169
170 bdr.cursor_area = &area;
171 bdr.area_region_type = art;
174}
175
177 const int mval[2],
178 const BoneDropper &bdr)
179{
180 Base *base = nullptr;
181
182 switch (CTX_data_mode_enum(C)) {
183 case CTX_MODE_POSE: {
184 bPoseChannel *bone = ED_armature_pick_pchan(C, mval, true, &base);
185 if (!bone || !base) {
187 }
188 Object *ob = base->object;
189 bArmature *armature = (bArmature *)ob->data;
190 if (!armature || &armature->id != bdr.search_ptr.owner_id) {
192 }
193
194 BoneSampleData sample_data;
195 sample_data.name = bone->name;
196 /* Not using the search pointer owner ID because pose bones are part of the object. */
197 sample_data.bone_rna = RNA_pointer_create_discrete(&base->object->id, &RNA_PoseBone, bone);
199 return sample_data;
200 }
201
203 EditBone *ebone = ED_armature_pick_ebone(C, mval, true, &base);
204 if (!ebone || !base) {
206 }
207 Object *ob = base->object;
208 bArmature *armature = (bArmature *)ob->data;
209 if (!armature || &armature->id != bdr.search_ptr.owner_id) {
211 }
212
213 BoneSampleData sample_data;
214 sample_data.name = ebone->name;
215 sample_data.bone_rna = RNA_pointer_create_discrete(&armature->id, &RNA_EditBone, ebone);
217 return sample_data;
218 }
219
220 default:
222 }
223}
224
226 const int mval[2],
227 const BoneDropper &bdr)
228{
229 BoneSampleData sample_data;
230
231 const bool success = ED_outliner_give_rna_under_cursor(C, mval, &sample_data.bone_rna);
232 if (!success) {
234 return sample_data;
235 }
236 ID *bone_id = sample_data.bone_rna.owner_id;
237 ID *search_id = bdr.search_ptr.owner_id;
238
239 /* By comparing the ID of the RNA returned by the outliner with the ID we are searching in, we
240 * can determine if the Bone is for the correct armature. */
241 if (sample_data.bone_rna.type == &RNA_Bone) {
242 if (bone_id != search_id) {
244 return sample_data;
245 }
246 Bone *bone = (Bone *)sample_data.bone_rna.data;
247 sample_data.name = bone->name;
249 return sample_data;
250 }
251
252 if (sample_data.bone_rna.type == &RNA_EditBone) {
253 if (bone_id != search_id) {
255 return sample_data;
256 }
257 EditBone *bone = (EditBone *)sample_data.bone_rna.data;
258 sample_data.name = bone->name;
260 return sample_data;
261 }
262
263 if (sample_data.bone_rna.type == &RNA_PoseBone) {
264 bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data;
265 /* Special case for pose bones. Because they are not stored in the Armature, the IDs of the
266 * search property and the picked result might not match since the comparison would be between
267 * armature and object. */
268 if (bdr.search_ptr.type == &RNA_Object) {
269 if (bone_id != search_id) {
271 return sample_data;
272 }
273 }
274 /* If looking for an armature, get the Armature object and follow the data pointer. */
275 if (bdr.search_ptr.type == &RNA_Armature) {
276 /* Expecting Pose Bones to be stored on the object. */
277 BLI_assert(GS(sample_data.bone_rna.owner_id->name) == ID_OB);
278 Object *armature_object = (Object *)sample_data.bone_rna.owner_id;
279 if (armature_object->data != bdr.search_ptr.owner_id) {
281 return sample_data;
282 }
283 }
284 sample_data.name = pose_bone->name;
286 return sample_data;
287 }
288
290 return sample_data;
291}
292
294 bContext *C, wmWindow &win, ScrArea &area, BoneDropper &bdr, const int event_xy[2])
295{
297 return {};
298 }
299
300 ARegion *region = BKE_area_find_region_xy(&area, RGN_TYPE_WINDOW, event_xy);
301
302 if (!region) {
303 return {};
304 }
305
306 wmWindow *win_prev = CTX_wm_window(C);
307 ScrArea *area_prev = CTX_wm_area(C);
308 ARegion *region_prev = CTX_wm_region(C);
309
310 const int mval[2] = {event_xy[0] - region->winrct.xmin, event_xy[1] - region->winrct.ymin};
311
312 CTX_wm_window_set(C, &win);
313 CTX_wm_area_set(C, &area);
314 CTX_wm_region_set(C, region);
315
316 /* Unfortunately it's necessary to always draw else we leave stale text. */
317 ED_region_tag_redraw(region);
318
319 BoneSampleData sample_data;
320 switch (area.spacetype) {
321 case SPACE_VIEW3D: {
322 sample_data = sample_data_from_3d_view(C, mval, bdr);
323 break;
324 }
325 case SPACE_OUTLINER: {
326 sample_data = sample_data_from_outliner(C, mval, bdr);
327 break;
328 }
329
330 default:
332 break;
333 }
334
335 if (sample_data.name) {
336 STRNCPY_UTF8(bdr.name, sample_data.name);
337 copy_v2_v2_int(bdr.name_pos, mval);
338 }
339
340 CTX_wm_window_set(C, win_prev);
341 CTX_wm_area_set(C, area_prev);
342 CTX_wm_region_set(C, region_prev);
343
344 return sample_data;
345}
346
347static SampleResult bonedropper_sample(bContext *C, BoneDropper &bdr, const int event_xy[2])
348{
349 int event_xy_win[2];
350 wmWindow *win = nullptr;
351 ScrArea *area = nullptr;
352 eyedropper_win_area_find(C, event_xy, event_xy_win, &win, &area);
353
354 if (!win || !area) {
356 }
359 }
360
361 BoneSampleData sample_data = bonedropper_sample_pt(C, *win, *area, bdr, event_xy_win);
362 if (!sample_data.name) {
363 return sample_data.sample_result;
364 }
365
367 /* In case we are searching for a bone, convert the pointer from bPoseChannel. */
368 if (search_type == &RNA_Bone && sample_data.bone_rna.type == &RNA_PoseBone &&
369 bdr.search_ptr.type == &RNA_Armature)
370 {
371 /* We are searching for something in the armature but got a pose bone on the object, so we
372 * need to do a conversion. We will just assume the ID under the cursor is the one we are
373 * searching for since there is no way to get the armature ID from the object ID that we
374 * have. */
375 bPoseChannel *pose_bone = (bPoseChannel *)sample_data.bone_rna.data;
377 bdr.search_ptr.owner_id, &RNA_Bone, pose_bone->bone);
378 }
379
381 switch (type) {
382 case PROP_STRING:
383 RNA_property_string_set(&bdr.ptr, bdr.prop, sample_data.name);
384 break;
385 case PROP_POINTER:
386 RNA_property_pointer_set(&bdr.ptr, bdr.prop, sample_data.bone_rna, CTX_wm_reports(C));
387 break;
388
389 default:
391 break;
392 }
393
394 RNA_property_update(C, &bdr.ptr, bdr.prop);
395
397}
398
400{
401 switch (result) {
403 BKE_report(op->reports, RPT_WARNING, "Picking a bone failed");
404 break;
407 op->reports, RPT_WARNING, "Picked bone does not belong to the already chosen armature");
408 break;
409
413 "Selection is not a bone. Armature needs to be in Pose Mode or Edit Mode "
414 "to pick in the 3D Viewport");
415 break;
416
418 BKE_report(op->reports, RPT_WARNING, "Selection is not a bone");
419 break;
420
422 BKE_report(op->reports, RPT_WARNING, "Can only pick from the 3D viewport or the outliner");
423 break;
424
426 break;
427 }
428}
429
431{
432 BoneDropper *bdr = (BoneDropper *)op->customdata;
433 if (!bdr) {
434 return OPERATOR_CANCELLED;
435 }
436
437 if (event->type == EVT_MODAL_MAP) {
438 switch (event->val) {
439 case EYE_MODAL_CANCEL:
441 return OPERATOR_CANCELLED;
443 const bool is_undo = bdr->is_undo;
444 const SampleResult result = bonedropper_sample(C, *bdr, event->xy);
445 bonedropper_exit(C, op);
447 /* Could support finished & undo-skip. */
448 return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
449 }
451 }
452 }
453 }
454 else if (event->type == MOUSEMOVE) {
455 bdr->name[0] = '\0';
456 int event_xy_win[2];
457 wmWindow *win = nullptr;
458 ScrArea *area = nullptr;
459 eyedropper_win_area_find(C, event->xy, event_xy_win, &win, &area);
460
461 if (win && area) {
462 /* Set the region for eyedropper cursor text drawing */
464 bonedropper_sample_pt(C, *win, *area, *bdr, event->xy);
465 }
466 }
467
469}
471{
472 /* This is needed to ensure viewport picking works. */
474
475 if (bonedropper_init(C, op)) {
476 wmWindow *win = CTX_wm_window(C);
477 /* Workaround for de-activating the button clearing the cursor, see #76794 */
480
483 }
484 return OPERATOR_CANCELLED;
485}
486
488{
489 if (bonedropper_init(C, op)) {
490 bonedropper_exit(C, op);
491
492 return OPERATOR_FINISHED;
493 }
494 return OPERATOR_CANCELLED;
495}
496
498{
500 PropertyRNA *prop;
501 int index_dummy;
502
503 if (CTX_wm_window(C) == nullptr) {
504 return false;
505 }
506
507 const Object *active_object = CTX_data_active_object(C);
508
509 if (!active_object || active_object->type != OB_ARMATURE) {
510 CTX_wm_operator_poll_msg_set(C, "The active object needs to be an armature");
511 return false;
512 }
513
514 if (!ELEM(active_object->mode, OB_MODE_POSE, OB_MODE_EDIT)) {
515 CTX_wm_operator_poll_msg_set(C, "The armature needs to be in Pose mode or Edit mode");
516 return false;
517 }
518
519 uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index_dummy);
520
521 if (!but) {
522 return false;
523 }
524
525 if (but->type != ButType::SearchMenu || !(but->flag & UI_BUT_VALUE_CLEAR)) {
526 return false;
527 }
528
529 uiButSearch *search_but = (uiButSearch *)but;
530
532 return false;
533 }
534
535 const StructRNA *type = RNA_property_pointer_type(&search_but->rnasearchpoin,
536 search_but->rnasearchprop);
537
538 return type == &RNA_Bone || type == &RNA_EditBone;
539}
540
542{
543 /* Identifiers. */
544 ot->name = "Eyedropper Bone";
545 ot->idname = "UI_OT_eyedropper_bone";
546 ot->description = "Sample a bone from the 3D View or the Outliner to store in a property";
547
548 /* API callbacks. */
549 ot->invoke = bonedropper_invoke;
550 ot->modal = bonedropper_modal;
551 ot->cancel = bonedropper_cancel;
552 ot->exec = bonedropper_exec;
553 ot->poll = bonedropper_poll;
554
555 /* Flags. */
557}
558
559} // 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)
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
ARegion * BKE_area_find_region_xy(const ScrArea *area, int regiontype, const int xy[2]) ATTR_NONNULL(3)
Definition screen.cc:875
SpaceType * BKE_spacetype_from_id(int spaceid)
Definition screen.cc:257
ARegionType * BKE_regiontype_from_id(const SpaceType *st, int regionid)
Definition screen.cc:267
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:846
#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 STRNCPY_UTF8(dst, src)
#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:693
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
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:161
@ PROP_STRING
Definition RNA_types.hh:165
@ PROP_POINTER
Definition RNA_types.hh:167
#define C
Definition RandGen.cpp:29
@ UI_BUT_UNDO
@ UI_BUT_VALUE_CLEAR
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)
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)
@ EYE_MODAL_CANCEL
@ EYE_MODAL_SAMPLE_CONFIRM
void eyedropper_draw_cursor_text_region(const int xy[2], const char *name)
#define GS(x)
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:414
char name[258]
Definition DNA_ID.h:432
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
ButType type
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int xy[2]
Definition WM_types.hh:761
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:4238
wmOperatorType * ot
Definition wm_files.cc:4237