2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * Contributor(s): Campbell Barton
20 * ***** END GPL LICENSE BLOCK *****
23 /** \file blender/editors/space_view3d/view3d_ruler.c
27 /* defines VIEW3D_OT_ruler modal operator */
29 #include "DNA_scene_types.h"
30 #include "DNA_object_types.h"
31 #include "DNA_gpencil_types.h"
33 #include "MEM_guardedalloc.h"
36 #include "BLI_blenlib.h"
38 #include "BLT_translation.h"
40 #include "BKE_context.h"
42 #include "BKE_gpencil.h"
49 #include "ED_screen.h"
50 #include "ED_view3d.h"
51 #include "ED_transform_snap_object_context.h"
52 #include "ED_space_api.h"
55 #include "BIF_glutil.h"
57 #include "UI_resources.h"
58 #include "UI_interface.h"
60 #include "view3d_intern.h" /* own include */
62 #define MVAL_MAX_PX_DIST 12.0f
65 /* -------------------------------------------------------------------- */
66 /* Ruler Item (we can have many) */
68 RULERITEM_USE_ANGLE = (1 << 0), /* use protractor */
69 RULERITEM_USE_RAYCAST = (1 << 1)
73 RULERITEM_DIRECTION_IN = 0,
74 RULERITEM_DIRECTION_OUT
77 /* keep smaller then selection, since we may want click elsewhere without selecting a ruler */
78 #define RULER_PICK_DIST 12.0f
79 #define RULER_PICK_DIST_SQ (RULER_PICK_DIST * RULER_PICK_DIST)
81 typedef struct RulerItem {
82 struct RulerItem *next, *prev;
84 /* worldspace coords, middle being optional */
88 char co_index; /* 0 -> 2*/
91 int raycast_dir; /* RULER_DIRECTION_* */
95 /* -------------------------------------------------------------------- */
96 /* Ruler Info (one per session) */
99 RULER_STATE_NORMAL = 0,
104 RULER_SNAP_OK = (1 << 0),
107 typedef struct RulerInfo {
113 float drag_start_co[3];
115 struct SnapObjectContext *snap_context;
120 void *draw_handle_pixel;
121 ARegion *ar; /* re-assigned every modal update */
124 /* -------------------------------------------------------------------- */
125 /* local functions */
126 static RulerItem *ruler_item_add(RulerInfo *ruler_info)
128 RulerItem *ruler_item = MEM_callocN(sizeof(RulerItem), "RulerItem");
129 BLI_addtail(&ruler_info->items, ruler_item);
133 static void ruler_item_remove(RulerInfo *ruler_info, RulerItem *ruler_item)
135 BLI_remlink(&ruler_info->items, ruler_item);
137 MEM_freeN(ruler_item);
140 static RulerItem *ruler_item_active_get(RulerInfo *ruler_info)
142 return BLI_findlink(&ruler_info->items, ruler_info->item_active);
145 static void ruler_item_active_set(RulerInfo *ruler_info, RulerItem *ruler_item)
147 ruler_info->item_active = BLI_findindex(&ruler_info->items, ruler_item);
150 static void ruler_item_as_string(RulerItem *ruler_item, UnitSettings *unit,
151 char *numstr, size_t numstr_size, int prec)
153 const bool do_split = (unit->flag & USER_UNIT_OPT_SPLIT) != 0;
155 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
156 const float ruler_angle = angle_v3v3v3(ruler_item->co[0],
160 if (unit->system == USER_UNIT_NONE) {
161 BLI_snprintf(numstr, numstr_size, "%.*f°", prec, RAD2DEGF(ruler_angle));
164 bUnit_AsString(numstr, numstr_size,
166 prec, unit->system, B_UNIT_ROTATION, do_split, false);
170 const float ruler_len = len_v3v3(ruler_item->co[0],
173 if (unit->system == USER_UNIT_NONE) {
174 BLI_snprintf(numstr, numstr_size, "%.*f", prec, ruler_len);
177 bUnit_AsString(numstr, numstr_size,
178 (double)(ruler_len * unit->scale_length),
179 prec, unit->system, B_UNIT_LENGTH, do_split, false);
185 static bool view3d_ruler_pick(RulerInfo *ruler_info, const float mval[2],
186 RulerItem **r_ruler_item, int *r_co_index)
188 ARegion *ar = ruler_info->ar;
189 RulerItem *ruler_item;
191 float dist_best = RULER_PICK_DIST_SQ;
192 RulerItem *ruler_item_best = NULL;
193 int co_index_best = -1;
195 for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
200 /* should these be checked? - ok for now not to */
201 for (j = 0; j < 3; j++) {
202 ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
205 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
206 dist = min_ff(dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[1]),
207 dist_squared_to_line_segment_v2(mval, co_ss[1], co_ss[2]));
208 if (dist < dist_best) {
210 ruler_item_best = ruler_item;
213 const float dist_points[3] = {
214 len_squared_v2v2(co_ss[0], mval),
215 len_squared_v2v2(co_ss[1], mval),
216 len_squared_v2v2(co_ss[2], mval),
218 if (min_fff(UNPACK3(dist_points)) < RULER_PICK_DIST_SQ) {
219 co_index_best = min_axis_v3(dist_points);
228 dist = dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[2]);
229 if (dist < dist_best) {
231 ruler_item_best = ruler_item;
234 const float dist_points[2] = {
235 len_squared_v2v2(co_ss[0], mval),
236 len_squared_v2v2(co_ss[2], mval),
238 if (min_ff(UNPACK2(dist_points)) < RULER_PICK_DIST_SQ) {
239 co_index_best = (dist_points[0] < dist_points[1]) ? 0 : 2;
249 if (ruler_item_best) {
250 *r_ruler_item = ruler_item_best;
251 *r_co_index = co_index_best;
255 *r_ruler_item = NULL;
262 * Ensure the 'snap_context' is only cached while dragging,
263 * needed since the user may toggle modes between tool use.
265 static void ruler_state_set(bContext *C, RulerInfo *ruler_info, int state)
267 if (state == ruler_info->state) {
272 if (ruler_info->snap_context) {
273 ED_transform_snap_object_context_destroy(ruler_info->snap_context);
274 ruler_info->snap_context = NULL;
277 if (state == RULER_STATE_NORMAL) {
280 else if (state == RULER_STATE_DRAG) {
281 ruler_info->snap_context = ED_transform_snap_object_context_create_view3d(
282 CTX_data_main(C), CTX_data_scene(C), 0,
283 ruler_info->ar, CTX_wm_view3d(C));
289 ruler_info->state = state;
292 #define RULER_ID "RulerData3D"
293 static bool view3d_ruler_to_gpencil(bContext *C, RulerInfo *ruler_info)
295 Scene *scene = CTX_data_scene(C);
299 bGPDpalette *palette;
300 bGPDpalettecolor *palcolor;
301 RulerItem *ruler_item;
302 const char *ruler_name = RULER_ID;
303 bool changed = false;
305 if (scene->gpd == NULL) {
306 scene->gpd = BKE_gpencil_data_addnew("GPencil");
309 gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
311 gpl = BKE_gpencil_layer_addnew(scene->gpd, ruler_name, false);
313 gpl->flag |= GP_LAYER_HIDE;
316 /* try to get active palette or create a new one */
317 palette = BKE_gpencil_palette_getactive(scene->gpd);
318 if (palette == NULL) {
319 palette = BKE_gpencil_palette_addnew(scene->gpd, DATA_("GP_Palette"), true);
321 /* try to get color with the ruler name or create a new one */
322 palcolor = BKE_gpencil_palettecolor_getbyname(palette, (char *)ruler_name);
323 if (palcolor == NULL) {
324 palcolor = BKE_gpencil_palettecolor_addnew(palette, (char *)ruler_name, true);
327 gpf = BKE_gpencil_layer_getframe(gpl, CFRA, true);
328 BKE_gpencil_free_strokes(gpf);
330 for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
334 /* allocate memory for a new stroke */
335 gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
336 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
338 pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
339 for (j = 0; j < 3; j++) {
340 copy_v3_v3(&pt->x, ruler_item->co[j]);
348 pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
349 for (j = 0; j < 3; j += 2) {
350 copy_v3_v3(&pt->x, ruler_item->co[j]);
356 gps->flag = GP_STROKE_3DSPACE;
358 /* assign color to stroke */
359 BLI_strncpy(gps->colorname, palcolor->info, sizeof(gps->colorname));
360 gps->palcolor = palcolor;
361 BLI_addtail(&gpf->strokes, gps);
368 static bool view3d_ruler_from_gpencil(bContext *C, RulerInfo *ruler_info)
370 Scene *scene = CTX_data_scene(C);
371 bool changed = false;
375 const char *ruler_name = RULER_ID;
376 gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
379 gpf = BKE_gpencil_layer_getframe(gpl, CFRA, false);
382 for (gps = gpf->strokes.first; gps; gps = gps->next) {
383 bGPDspoint *pt = gps->points;
385 if (gps->totpoints == 3) {
386 RulerItem *ruler_item = ruler_item_add(ruler_info);
387 for (j = 0; j < 3; j++) {
388 copy_v3_v3(ruler_item->co[j], &pt->x);
391 ruler_item->flag |= RULERITEM_USE_ANGLE;
394 else if (gps->totpoints == 2) {
395 RulerItem *ruler_item = ruler_item_add(ruler_info);
396 for (j = 0; j < 3; j += 2) {
397 copy_v3_v3(ruler_item->co[j], &pt->x);
410 /* -------------------------------------------------------------------- */
411 /* local callbacks */
413 static void ruler_info_draw_pixel(const struct bContext *C, ARegion *ar, void *arg)
415 Scene *scene = CTX_data_scene(C);
416 UnitSettings *unit = &scene->unit;
417 RulerItem *ruler_item;
418 RulerInfo *ruler_info = arg;
419 RegionView3D *rv3d = ar->regiondata;
420 // ARegion *ar = ruler_info->ar;
421 const float cap_size = 4.0f;
422 const float bg_margin = 4.0f * U.pixelsize;
423 const float bg_radius = 4.0f * U.pixelsize;
424 const float arc_size = 64.0f * U.pixelsize;
426 const int arc_steps = ARC_STEPS;
428 //unsigned int color_act = 0x666600;
429 unsigned int color_act = 0xffffff;
430 unsigned int color_base = 0x0;
431 unsigned char color_back[4] = {0xff, 0xff, 0xff, 0x80};
432 unsigned char color_text[3];
433 unsigned char color_wire[3];
435 /* anti-aliased lines for more consistent appearance */
436 glEnable(GL_LINE_SMOOTH);
438 BLF_enable(blf_mono_font, BLF_ROTATION);
439 BLF_size(blf_mono_font, 14 * U.pixelsize, U.dpi);
440 BLF_rotation(blf_mono_font, 0.0f);
442 UI_GetThemeColor3ubv(TH_TEXT, color_text);
443 UI_GetThemeColor3ubv(TH_WIRE, color_wire);
445 for (ruler_item = ruler_info->items.first, i = 0; ruler_item; ruler_item = ruler_item->next, i++) {
446 const bool is_act = (i == ruler_info->item_active);
451 /* should these be checked? - ok for now not to */
452 for (j = 0; j < 3; j++) {
453 ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
458 cpack(is_act ? color_act : color_base);
460 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
461 glBegin(GL_LINE_STRIP);
462 for (j = 0; j < 3; j++) {
463 glVertex2fv(co_ss[j]);
468 glBegin(GL_LINE_STRIP);
469 for (j = 0; j < 3; j++) {
470 glVertex2fv(co_ss[j]);
479 float arc_ss_coords[ARC_STEPS + 1][2];
486 const float px_scale = (ED_view3d_pixel_size(rv3d, ruler_item->co[1]) *
488 len_v2v2(co_ss[0], co_ss[1]) / 2.0f,
489 len_v2v2(co_ss[2], co_ss[1]) / 2.0f));
491 sub_v3_v3v3(dir_a, ruler_item->co[0], ruler_item->co[1]);
492 sub_v3_v3v3(dir_b, ruler_item->co[2], ruler_item->co[1]);
496 cross_v3_v3v3(axis, dir_a, dir_b);
497 angle = angle_normalized_v3v3(dir_a, dir_b);
499 axis_angle_to_quat(quat, axis, angle / arc_steps);
501 copy_v3_v3(dir_tmp, dir_a);
503 glColor3ubv(color_wire);
505 for (j = 0; j <= arc_steps; j++) {
506 madd_v3_v3v3fl(co_tmp, ruler_item->co[1], dir_tmp, px_scale);
507 ED_view3d_project_float_global(ar, co_tmp, arc_ss_coords[j], V3D_PROJ_TEST_NOP);
508 mul_qt_v3(quat, dir_tmp);
511 glEnableClientState(GL_VERTEX_ARRAY);
512 glVertexPointer(2, GL_FLOAT, 0, arc_ss_coords);
513 glDrawArrays(GL_LINE_STRIP, 0, arc_steps + 1);
514 glDisableClientState(GL_VERTEX_ARRAY);
520 float numstr_size[2];
522 const int prec = 2; /* XXX, todo, make optional */
524 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
526 BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
528 pos[0] = co_ss[1][0] + (cap_size * 2.0f);
529 pos[1] = co_ss[1][1] - (numstr_size[1] / 2.0f);
532 glColor4ubv(color_back);
533 UI_draw_roundbox_corner_set(UI_CNR_ALL);
535 pos[0] - bg_margin, pos[1] - bg_margin,
536 pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
539 glColor3ubv(color_text);
540 BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
541 BLF_rotation(blf_mono_font, 0.0f);
542 BLF_draw(blf_mono_font, numstr, sizeof(numstr));
547 float rot_90_vec_a[2];
548 float rot_90_vec_b[2];
551 sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[1]);
552 rot_90_vec_a[0] = -dir_ruler[1];
553 rot_90_vec_a[1] = dir_ruler[0];
554 normalize_v2(rot_90_vec_a);
556 sub_v2_v2v2(dir_ruler, co_ss[1], co_ss[2]);
557 rot_90_vec_b[0] = -dir_ruler[1];
558 rot_90_vec_b[1] = dir_ruler[0];
559 normalize_v2(rot_90_vec_b);
563 glColor3ubv(color_wire);
567 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, cap_size);
569 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, -cap_size);
572 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, cap_size);
574 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, -cap_size);
578 glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] - cap_size);
579 glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] + cap_size);
580 glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] + cap_size);
581 glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] - cap_size);
588 glBegin(GL_LINE_STRIP);
589 for (j = 0; j < 3; j += 2) {
590 glVertex2fv(co_ss[j]);
595 glBegin(GL_LINE_STRIP);
596 for (j = 0; j < 3; j += 2) {
597 glVertex2fv(co_ss[j]);
602 sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]);
607 float numstr_size[2];
608 const int prec = 6; /* XXX, todo, make optional */
611 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
613 BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
615 mid_v2_v2v2(pos, co_ss[0], co_ss[2]);
618 pos[0] -= numstr_size[0] / 2.0f;
619 pos[1] -= numstr_size[1] / 2.0f;
622 glColor4ubv(color_back);
623 UI_draw_roundbox_corner_set(UI_CNR_ALL);
624 UI_draw_roundbox(pos[0] - bg_margin, pos[1] - bg_margin,
625 pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
628 glColor3ubv(color_text);
629 BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
630 BLF_draw(blf_mono_font, numstr, sizeof(numstr));
635 float rot_90_vec[2] = {-dir_ruler[1], dir_ruler[0]};
638 normalize_v2(rot_90_vec);
641 glColor3ubv(color_wire);
644 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, cap_size);
646 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, -cap_size);
649 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, cap_size);
651 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, -cap_size);
660 glDisable(GL_LINE_SMOOTH);
662 BLF_disable(blf_mono_font, BLF_ROTATION);
667 if ((ruler_info->snap_flag & RULER_SNAP_OK) && (ruler_info->state == RULER_STATE_DRAG)) {
668 ruler_item = ruler_item_active_get(ruler_info);
670 /* size from drawSnapping */
671 const float size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
673 ED_view3d_project_float_global(ar, ruler_item->co[ruler_item->co_index], co_ss, V3D_PROJ_TEST_NOP);
676 circ(co_ss[0], co_ss[1], size * U.pixelsize);
682 /* free, use for both cancel and finish */
683 static void view3d_ruler_end(const struct bContext *UNUSED(C), RulerInfo *ruler_info)
685 ED_region_draw_cb_exit(ruler_info->ar->type, ruler_info->draw_handle_pixel);
688 static void view3d_ruler_free(RulerInfo *ruler_info)
690 BLI_freelistN(&ruler_info->items);
692 if (ruler_info->snap_context) {
693 ED_transform_snap_object_context_destroy(ruler_info->snap_context);
696 MEM_freeN(ruler_info);
699 static void view3d_ruler_item_project(RulerInfo *ruler_info, float r_co[3],
702 ED_view3d_win_to_3d_int(ruler_info->sa->spacedata.first, ruler_info->ar, r_co, xy, r_co);
705 /* use for mousemove events */
706 static bool view3d_ruler_item_mousemove(
707 RulerInfo *ruler_info, const int mval[2],
708 const bool do_thickness, const bool do_snap)
710 const float eps_bias = 0.0002f;
711 float dist_px = MVAL_MAX_PX_DIST * U.pixelsize; /* snap dist */
712 RulerItem *ruler_item = ruler_item_active_get(ruler_info);
714 ruler_info->snap_flag &= ~RULER_SNAP_OK;
717 float *co = ruler_item->co[ruler_item->co_index];
718 /* restore the initial depth */
719 copy_v3_v3(co, ruler_info->drag_start_co);
720 view3d_ruler_item_project(ruler_info, co, mval);
721 if (do_thickness && ruler_item->co_index != 1) {
722 // Scene *scene = CTX_data_scene(C);
723 // View3D *v3d = ruler_info->sa->spacedata.first;
724 const float mval_fl[2] = {UNPACK2(mval)};
729 co_other = ruler_item->co[ruler_item->co_index == 0 ? 2 : 0];
731 if (ED_transform_snap_object_project_view3d_mixed(
732 ruler_info->snap_context,
734 &(const struct SnapObjectParams){
735 .snap_select = SNAP_ALL,
736 .use_object_edit_cage = true,
738 mval_fl, &dist_px, true,
741 negate_v3(ray_normal);
743 madd_v3_v3v3fl(ray_start, co, ray_normal, eps_bias);
744 ED_transform_snap_object_project_ray(
745 ruler_info->snap_context,
746 &(const struct SnapObjectParams){
747 .snap_select = SNAP_ALL,
748 .use_object_edit_cage = true,
750 ray_start, ray_normal, NULL,
755 // Scene *scene = CTX_data_scene(C);
756 View3D *v3d = ruler_info->sa->spacedata.first;
757 const float mval_fl[2] = {UNPACK2(mval)};
758 bool use_depth = (v3d->drawtype >= OB_SOLID);
760 if (ED_transform_snap_object_project_view3d_mixed(
761 ruler_info->snap_context,
762 (SCE_SELECT_VERTEX | SCE_SELECT_EDGE) | (use_depth ? SCE_SELECT_FACE : 0),
763 &(const struct SnapObjectParams){
764 .snap_select = SNAP_ALL,
765 .use_object_edit_cage = true,
767 mval_fl, &dist_px, use_depth,
770 ruler_info->snap_flag |= RULER_SNAP_OK;
780 static void view3d_ruler_header_update(ScrArea *sa)
782 const char *text = IFACE_("Ctrl+LMB: Add, "
785 "Shift+Drag: Thickness, "
786 "Ctrl+C: Copy Value, "
790 ED_area_headerprint(sa, text);
793 /* -------------------------------------------------------------------- */
794 /* Operator callbacks */
796 static int view3d_ruler_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
798 wmWindow *win = CTX_wm_window(C);
799 ScrArea *sa = CTX_wm_area(C);
800 ARegion *ar = CTX_wm_region(C);
801 RulerInfo *ruler_info;
803 ruler_info = MEM_callocN(sizeof(RulerInfo), "RulerInfo");
805 if (view3d_ruler_from_gpencil(C, ruler_info)) {
806 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
809 op->customdata = ruler_info;
811 ruler_info->win = win;
813 ruler_info->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ruler_info_draw_pixel,
814 ruler_info, REGION_DRAW_POST_PIXEL);
816 view3d_ruler_header_update(sa);
818 op->flag |= OP_IS_MODAL_CURSOR_REGION;
820 WM_cursor_modal_set(win, BC_CROSSCURSOR);
821 WM_event_add_modal_handler(C, op);
823 return OPERATOR_RUNNING_MODAL;
826 static void view3d_ruler_cancel(bContext *C, wmOperator *op)
828 RulerInfo *ruler_info = op->customdata;
830 view3d_ruler_end(C, ruler_info);
831 view3d_ruler_free(ruler_info);
832 op->customdata = NULL;
835 static int view3d_ruler_modal(bContext *C, wmOperator *op, const wmEvent *event)
837 bool do_draw = false;
838 int exit_code = OPERATOR_RUNNING_MODAL;
839 RulerInfo *ruler_info = op->customdata;
840 ScrArea *sa = CTX_wm_area(C);
841 ARegion *ar = CTX_wm_region(C);
842 RegionView3D *rv3d = ar->regiondata;
844 /* its possible to change spaces while running the operator [#34894] */
845 if (UNLIKELY(sa != ruler_info->sa)) {
846 exit_code = OPERATOR_FINISHED;
852 switch (event->type) {
854 if (event->val == KM_RELEASE) {
855 if (ruler_info->state == RULER_STATE_DRAG) {
856 /* rubber-band angle removal */
857 RulerItem *ruler_item = ruler_item_active_get(ruler_info);
858 if (ruler_item && (ruler_item->co_index == 1) && (ruler_item->flag & RULERITEM_USE_ANGLE)) {
859 if (!BLI_rcti_isect_pt_v(&ar->winrct, &event->x)) {
860 ruler_item->flag &= ~RULERITEM_USE_ANGLE;
864 if (ruler_info->snap_flag & RULER_SNAP_OK) {
865 ruler_info->snap_flag &= ~RULER_SNAP_OK;
868 ruler_state_set(C, ruler_info, RULER_STATE_NORMAL);
872 if (ruler_info->state == RULER_STATE_NORMAL) {
875 /* weak - but user friendly */
876 BLI_listbase_is_empty(&ruler_info->items))
878 View3D *v3d = CTX_wm_view3d(C);
879 const bool use_depth = (v3d->drawtype >= OB_SOLID);
881 /* Create new line */
882 RulerItem *ruler_item_prev = ruler_item_active_get(ruler_info);
883 RulerItem *ruler_item;
884 /* check if we want to drag an existing point or add a new one */
885 ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
887 ruler_item = ruler_item_add(ruler_info);
888 ruler_item_active_set(ruler_info, ruler_item);
891 /* snap the first point added, not essential but handy */
892 ruler_item->co_index = 0;
893 view3d_ruler_item_mousemove(ruler_info, event->mval, false, true);
894 copy_v3_v3(ruler_info->drag_start_co, ruler_item->co[ruler_item->co_index]);
897 /* initial depth either previous ruler, view offset */
898 if (ruler_item_prev) {
899 copy_v3_v3(ruler_info->drag_start_co, ruler_item_prev->co[ruler_item_prev->co_index]);
902 negate_v3_v3(ruler_info->drag_start_co, rv3d->ofs);
905 copy_v3_v3(ruler_item->co[0], ruler_info->drag_start_co);
906 view3d_ruler_item_project(ruler_info, ruler_item->co[0], event->mval);
909 copy_v3_v3(ruler_item->co[2], ruler_item->co[0]);
910 ruler_item->co_index = 2;
915 float mval_fl[2] = {UNPACK2(event->mval)};
916 RulerItem *ruler_item_pick;
919 /* select and drag */
920 if (view3d_ruler_pick(ruler_info, mval_fl, &ruler_item_pick, &co_index)) {
921 if (co_index == -1) {
922 if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
923 /* Add Center Point */
924 ruler_item_active_set(ruler_info, ruler_item_pick);
925 ruler_item_pick->flag |= RULERITEM_USE_ANGLE;
926 ruler_item_pick->co_index = 1;
927 ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
929 /* find the factor */
934 ED_view3d_project_float_global(ar, ruler_item_pick->co[0], co_ss[0], V3D_PROJ_TEST_NOP);
935 ED_view3d_project_float_global(ar, ruler_item_pick->co[2], co_ss[1], V3D_PROJ_TEST_NOP);
937 fac = line_point_factor_v2(mval_fl, co_ss[0], co_ss[1]);
938 CLAMP(fac, 0.0f, 1.0f);
940 interp_v3_v3v3(ruler_item_pick->co[1],
941 ruler_item_pick->co[0],
942 ruler_item_pick->co[2], fac);
945 /* update the new location */
946 view3d_ruler_item_mousemove(ruler_info, event->mval,
947 event->shift != 0, event->ctrl != 0);
952 ruler_item_active_set(ruler_info, ruler_item_pick);
953 ruler_item_pick->co_index = co_index;
954 ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
956 /* store the initial depth */
957 copy_v3_v3(ruler_info->drag_start_co, ruler_item_pick->co[ruler_item_pick->co_index]);
963 exit_code = OPERATOR_PASS_THROUGH;
973 RulerItem *ruler_item = ruler_item_active_get(ruler_info);
977 Scene *scene = CTX_data_scene(C);
978 UnitSettings *unit = &scene->unit;
980 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
981 WM_clipboard_text_set((void *) numstr, false);
989 WM_event_add_mousemove(C);
994 if (ruler_info->state == RULER_STATE_DRAG) {
995 if (view3d_ruler_item_mousemove(ruler_info, event->mval,
996 event->shift != 0, event->ctrl != 0))
1006 exit_code = OPERATOR_CANCELLED;
1011 /* Enter may be used to invoke from search. */
1012 if (event->val == KM_PRESS) {
1013 view3d_ruler_to_gpencil(C, ruler_info);
1015 exit_code = OPERATOR_FINISHED;
1021 if (event->val == KM_PRESS) {
1022 if (ruler_info->state == RULER_STATE_NORMAL) {
1023 RulerItem *ruler_item = ruler_item_active_get(ruler_info);
1025 RulerItem *ruler_item_other = ruler_item->prev ? ruler_item->prev : ruler_item->next;
1026 ruler_item_remove(ruler_info, ruler_item);
1027 ruler_item_active_set(ruler_info, ruler_item_other);
1035 exit_code = OPERATOR_PASS_THROUGH;
1040 if (ruler_info->state == RULER_STATE_DRAG) {
1041 op->flag &= ~OP_IS_MODAL_CURSOR_REGION;
1044 op->flag |= OP_IS_MODAL_CURSOR_REGION;
1048 view3d_ruler_header_update(sa);
1050 /* all 3d views draw rulers */
1051 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
1055 if (ELEM(exit_code, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
1056 WM_cursor_modal_restore(ruler_info->win);
1058 view3d_ruler_end(C, ruler_info);
1059 view3d_ruler_free(ruler_info);
1060 op->customdata = NULL;
1062 ED_area_headerprint(sa, NULL);
1068 void VIEW3D_OT_ruler(wmOperatorType *ot)
1071 ot->name = "Ruler/Protractor";
1072 ot->description = "Interactive ruler";
1073 ot->idname = "VIEW3D_OT_ruler";
1076 ot->invoke = view3d_ruler_invoke;
1077 ot->cancel = view3d_ruler_cancel;
1078 ot->modal = view3d_ruler_modal;
1079 ot->poll = ED_operator_view3d_active;