ruler snapping
[blender.git] / source / blender / editors / space_view3d / view3d_ruler.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
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.
8  *
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.
13  *
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.
17  *
18  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/space_view3d/view3d_ruler.c
24  *  \ingroup spview3d
25  */
26
27 /* defines VIEW3D_OT_ruler modal operator */
28
29 #include "DNA_scene_types.h"
30 #include "DNA_object_types.h"
31 #include "DNA_gpencil_types.h"
32
33 #include "MEM_guardedalloc.h"
34
35 #include "BLI_math.h"
36 #include "BLI_blenlib.h"
37
38 #include "BKE_context.h"
39 #include "BKE_unit.h"
40 #include "BKE_gpencil.h"
41
42 #include "BIF_gl.h"
43
44 #include "WM_api.h"
45 #include "WM_types.h"
46
47 #include "ED_screen.h"
48 #include "ED_space_api.h"
49
50 #include "BLF_api.h"
51 #include "BIF_glutil.h"
52
53 #include "UI_resources.h"
54 #include "UI_interface.h"
55
56 #include "view3d_intern.h"  /* own include */
57
58
59 /* -------------------------------------------------------------------- */
60 /* Snapping (could be own function) */
61 /* NOTE - this is not very nice use of transform snapping */
62 #include "ED_transform.h"
63
64 #define MVAL_MAX_PX_DIST 12.0f
65
66 /**
67  * Convenience function for performing snapping.
68  *
69  * \param C  Context.
70  * \param r_co  hit location.
71  * \param r_no  hit normal (optional).
72  * \param co_ss  Screenspace coordinate.
73  * \param use_depth  Snap to the closest element, use when using more then one snap type.
74  * \param use_vert  Snap to verts.
75  * \param use_edge  Snap to edges.
76  * \param use_face  Snap to faces.
77  * \return Snap success
78  */
79 static bool ED_view3d_snap_co(bContext *C, float r_co[3], float r_no[3], const float co_ss[2], bool use_depth,
80                               bool use_vert, bool use_edge, bool use_face)
81 {
82         float dist_px = MVAL_MAX_PX_DIST;  /* snap dist */
83         float r_no_dummy[3];
84         float ray_dist = TRANSFORM_DIST_MAX_RAY;
85         bool ret = false;
86         float *r_no_ptr = r_no ? r_no : r_no_dummy;
87
88         Scene *scene = CTX_data_scene(C);
89         View3D *v3d = CTX_wm_view3d(C);
90         ARegion *ar = CTX_wm_region(C);
91         struct Object *obedit = CTX_data_edit_object(C);
92
93         BLI_assert(use_vert || use_edge || use_face);
94
95         /* try snap edge, then face if it fails */
96         if (use_vert) {
97                 ret |= snapObjectsEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_VERTEX,
98                                      co_ss, &dist_px, r_co, r_no_ptr, &ray_dist, SNAP_ALL);
99         }
100         if (use_edge && (ret == false || use_depth)) {
101                 if (use_depth == false) ray_dist = TRANSFORM_DIST_MAX_RAY;
102                 ret |= snapObjectsEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_EDGE,
103                                      co_ss, &dist_px, r_co, r_no_ptr, &ray_dist, SNAP_ALL);
104         }
105         if (use_face && (ret == false || use_depth)) {
106                 if (use_depth == false) ray_dist = TRANSFORM_DIST_MAX_RAY;
107                 ret |= snapObjectsEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_FACE,
108                                      co_ss, &dist_px, r_co, r_no_ptr, &ray_dist, SNAP_ALL);
109         }
110
111         return ret;
112 }
113
114 static bool ED_view3d_snap_ray(bContext *C, float r_co[3],
115                                const float ray_start[3], const float ray_normal[3])
116 {
117         float dist_px = MVAL_MAX_PX_DIST;  /* snap dist */
118         float r_no_dummy[3];
119         float ray_dist = TRANSFORM_DIST_MAX_RAY;
120         bool ret;
121
122         Scene *scene = CTX_data_scene(C);
123         View3D *v3d = CTX_wm_view3d(C);
124         ARegion *ar = CTX_wm_region(C);
125         struct Object *obedit = CTX_data_edit_object(C);
126
127         /* try snap edge, then face if it fails */
128         ret = snapObjectsRayEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_FACE,
129                                NULL, NULL,
130                                ray_start, ray_normal, &ray_dist,
131                                NULL, &dist_px, r_co, r_no_dummy, SNAP_ALL);
132
133         return ret;
134 }
135 /* done snapping */
136
137
138 /* -------------------------------------------------------------------- */
139 /* Ruler Item (we can have many) */
140 enum {
141         RULERITEM_USE_ANGLE = (1 << 0),  /* use protractor */
142         RULERITEM_USE_RAYCAST = (1 << 1)
143 };
144
145 enum {
146         RULERITEM_DIRECTION_IN = 0,
147         RULERITEM_DIRECTION_OUT
148 };
149
150 /* keep smaller then selection, since we may want click elsewhere without selecting a ruler */
151 #define RULER_PICK_DIST 12.0f
152 #define RULER_PICK_DIST_SQ (RULER_PICK_DIST * RULER_PICK_DIST)
153
154 typedef struct RulerItem {
155         struct RulerItem *next, *prev;
156
157         /* worldspace coords, middle being optional */
158         float co[3][3];
159
160         /* selected coord */
161         char  co_index; /* 0 -> 2*/
162
163         int   flag;
164         int   raycast_dir;  /* RULER_DIRECTION_* */
165 } RulerItem;
166
167
168 /* -------------------------------------------------------------------- */
169 /* Ruler Info (one per session) */
170
171 enum {
172         RULER_STATE_NORMAL = 0,
173         RULER_STATE_DRAG
174 };
175
176 enum {
177         RULER_SNAP_OK = (1 << 0),
178 };
179
180 typedef struct RulerInfo {
181         ListBase items;
182         int      item_active;
183         int flag;
184         int snap_flag;
185         int state;
186         float drag_start_co[3];
187
188         /* wm state */
189         wmWindow *win;
190         ScrArea *sa;
191         ARegion *ar;
192         void *draw_handle_pixel;
193 } RulerInfo;
194
195 /* -------------------------------------------------------------------- */
196 /* local functions */
197 static RulerItem *ruler_item_add(RulerInfo *ruler_info)
198 {
199         RulerItem *ruler_item = MEM_callocN(sizeof(RulerItem), "RulerItem");
200         BLI_addtail(&ruler_info->items, ruler_item);
201         return ruler_item;
202 }
203
204 static void ruler_item_remove(RulerInfo *ruler_info, RulerItem *ruler_item)
205 {
206         BLI_remlink(&ruler_info->items, ruler_item);
207         MEM_freeN(ruler_item);
208 }
209
210 static RulerItem *ruler_item_active_get(RulerInfo *ruler_info)
211 {
212         return BLI_findlink(&ruler_info->items, ruler_info->item_active);
213 }
214
215 static void ruler_item_active_set(RulerInfo *ruler_info, RulerItem *ruler_item)
216 {
217         ruler_info->item_active = BLI_findindex(&ruler_info->items, ruler_item);
218 }
219
220 static void ruler_item_as_string(RulerItem *ruler_item, UnitSettings *unit,
221                                  char *numstr, size_t numstr_size, int prec)
222 {
223         const int do_split = unit->flag & USER_UNIT_OPT_SPLIT;
224
225         if (ruler_item->flag & RULERITEM_USE_ANGLE) {
226                 const float ruler_angle = angle_v3v3v3(ruler_item->co[0],
227                                                        ruler_item->co[1],
228                                                        ruler_item->co[2]);
229
230                 if (unit->system == USER_UNIT_NONE) {
231                         BLI_snprintf(numstr, numstr_size, "%.*f°", prec, RAD2DEGF(ruler_angle));
232                 }
233                 else {
234                         bUnit_AsString(numstr, numstr_size,
235                                        (double)ruler_angle,
236                                        prec, unit->system, B_UNIT_ROTATION, do_split, false);
237                 }
238         }
239         else {
240                 const float ruler_len = len_v3v3(ruler_item->co[0],
241                                                  ruler_item->co[2]);
242
243                 if (unit->system == USER_UNIT_NONE) {
244                         BLI_snprintf(numstr, numstr_size, "%.*f", prec, ruler_len);
245                 }
246                 else {
247                         bUnit_AsString(numstr, numstr_size,
248                                        (double)(ruler_len * unit->scale_length),
249                                        prec, unit->system, B_UNIT_LENGTH, do_split, false);
250                 }
251         }
252
253 }
254
255 static bool view3d_ruler_pick(RulerInfo *ruler_info, const float mval[2],
256                               RulerItem **r_ruler_item, int *r_co_index)
257 {
258         ARegion *ar = ruler_info->ar;
259         RulerItem *ruler_item;
260
261         float dist_best = RULER_PICK_DIST_SQ;
262         RulerItem *ruler_item_best = NULL;
263         int co_index_best = -1;
264
265         for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
266                 float co_ss[3][2];
267                 float dist;
268                 int j;
269
270                 /* should these be checked? - ok for now not to */
271                 for (j = 0; j < 3; j++) {
272                         ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
273                 }
274
275                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
276                         dist = min_ff(dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[1]),
277                                       dist_squared_to_line_segment_v2(mval, co_ss[1], co_ss[2]));
278                         if (dist < dist_best) {
279                                 dist_best = dist;
280                                 ruler_item_best = ruler_item;
281
282                                 {
283                                         float dist_points[3] = {len_squared_v2v2(co_ss[0], mval),
284                                                                 len_squared_v2v2(co_ss[1], mval),
285                                                                 len_squared_v2v2(co_ss[2], mval)};
286                                         if (min_fff(UNPACK3(dist_points)) < RULER_PICK_DIST_SQ) {
287                                                 co_index_best = min_axis_v3(dist_points);
288                                         }
289                                         else {
290                                                 co_index_best = -1;
291                                         }
292                                 }
293                         }
294                 }
295                 else {
296                         dist = dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[2]);
297                         if (dist < dist_best) {
298                                 dist_best = dist;
299                                 ruler_item_best = ruler_item;
300
301                                 {
302                                         float dist_points[2] = {len_squared_v2v2(co_ss[0], mval),
303                                                                 len_squared_v2v2(co_ss[2], mval)};
304                                         if (min_ff(UNPACK2(dist_points)) < RULER_PICK_DIST_SQ) {
305                                                 co_index_best = (dist_points[0] < dist_points[1]) ? 0 : 2;
306                                         }
307                                         else {
308                                                 co_index_best = -1;
309                                         }
310                                 }
311                         }
312                 }
313         }
314
315         if (ruler_item_best) {
316                 *r_ruler_item = ruler_item_best;
317                 *r_co_index = co_index_best;
318                 return true;
319         }
320         else {
321                 *r_ruler_item = NULL;
322                 *r_co_index = -1;
323                 return false;
324         }
325 }
326
327 #define RULER_ID "RulerData3D"
328 static bool view3d_ruler_to_gpencil(bContext *C, RulerInfo *ruler_info)
329 {
330         Scene *scene = CTX_data_scene(C);
331         bGPDlayer *gpl;
332         bGPDframe *gpf;
333         bGPDstroke *gps;
334         RulerItem *ruler_item;
335         const char *ruler_name = RULER_ID;
336         bool change = false;
337
338         if (scene->gpd == NULL) {
339                 scene->gpd = gpencil_data_addnew("GPencil");
340         }
341
342         gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
343         if (gpl == NULL) {
344                 gpl = gpencil_layer_addnew(scene->gpd, ruler_name, false);
345                 gpl->thickness = 1;
346                 gpl->flag |= GP_LAYER_HIDE;
347         }
348
349         gpf = gpencil_layer_getframe(gpl, CFRA, true);
350         free_gpencil_strokes(gpf);
351
352         for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
353                 bGPDspoint *pt;
354                 int j;
355
356                 /* allocate memory for a new stroke */
357                 gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
358                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
359                         gps->totpoints = 3;
360                         pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
361                         for (j = 0; j < 3; j++) {
362                                 copy_v3_v3(&pt->x, ruler_item->co[j]);
363                                 pt->pressure = 1.0f;
364                                 pt++;
365                         }
366                 }
367                 else {
368                         gps->totpoints = 2;
369                         pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
370                         for (j = 0; j < 3; j += 2) {
371                                 copy_v3_v3(&pt->x, ruler_item->co[j]);
372                                 pt->pressure = 1.0f;
373                                 pt++;
374                         }
375                 }
376                 gps->flag = GP_STROKE_3DSPACE;
377                 BLI_addtail(&gpf->strokes, gps);
378                 change = true;
379         }
380
381         return change;
382 }
383
384 static bool view3d_ruler_from_gpencil(bContext *C, RulerInfo *ruler_info)
385 {
386         Scene *scene = CTX_data_scene(C);
387         bool change = false;
388
389         if (scene->gpd) {
390                 bGPDlayer *gpl;
391                 const char *ruler_name = RULER_ID;
392                 gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
393                 if (gpl) {
394                         bGPDframe *gpf;
395                         gpf = gpencil_layer_getframe(gpl, CFRA, false);
396                         if (gpf) {
397                                 bGPDstroke *gps;
398                                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
399                                         bGPDspoint *pt = gps->points;
400                                         int j;
401                                         if (gps->totpoints == 3) {
402                                                 RulerItem *ruler_item = ruler_item_add(ruler_info);
403                                                 for (j = 0; j < 3; j++) {
404                                                         copy_v3_v3(ruler_item->co[j], &pt->x);
405                                                         pt++;
406                                                 }
407                                                 ruler_item->flag |= RULERITEM_USE_ANGLE;
408                                                 change = true;
409                                         }
410                                         else if (gps->totpoints == 2) {
411                                                 RulerItem *ruler_item = ruler_item_add(ruler_info);
412                                                 for (j = 0; j < 3; j += 2) {
413                                                         copy_v3_v3(ruler_item->co[j], &pt->x);
414                                                         pt++;
415                                                 }
416                                                 change = true;
417                                         }
418                                 }
419                         }
420                 }
421         }
422
423         return change;
424 }
425
426 /* -------------------------------------------------------------------- */
427 /* local callbacks */
428
429 static void ruler_info_draw_pixel(const struct bContext *C, ARegion *ar, void *arg)
430 {
431         Scene *scene = CTX_data_scene(C);
432         UnitSettings *unit = &scene->unit;
433         RulerItem *ruler_item;
434         RulerInfo *ruler_info = arg;
435         RegionView3D *rv3d = ruler_info->ar->regiondata;
436 //      ARegion *ar = ruler_info->ar;
437         const float cap_size = 4.0f;
438         const float bg_margin = 4.0f * U.pixelsize;
439         const float bg_radius = 4.0f * U.pixelsize;
440         const float arc_size = 64.0f * U.pixelsize;
441 #define ARC_STEPS 24
442         const int arc_steps = ARC_STEPS;
443         int i;
444         //unsigned int color_act = 0x666600;
445         unsigned int color_act = 0xffffff;
446         unsigned int color_base = 0x0;
447         unsigned char color_back[4] = {0xff, 0xff, 0xff, 0x80};
448         unsigned char color_text[3];
449         unsigned char color_wire[3];
450
451         /* anti-aliased lines for more consistent appearance */
452         glEnable(GL_LINE_SMOOTH);
453
454         BLF_enable(blf_mono_font, BLF_ROTATION);
455         BLF_size(blf_mono_font, 14 * U.pixelsize, U.dpi);
456         BLF_rotation(blf_mono_font, 0.0f);
457
458         UI_GetThemeColor3ubv(TH_TEXT, color_text);
459         UI_GetThemeColor3ubv(TH_WIRE, color_wire);
460
461         for (ruler_item = ruler_info->items.first, i = 0; ruler_item; ruler_item = ruler_item->next, i++) {
462                 const bool is_act = (i == ruler_info->item_active);
463                 float dir_ruler[2];
464                 float co_ss[3][2];
465                 int j;
466
467                 /* should these be checked? - ok for now not to */
468                 for (j = 0; j < 3; j++) {
469                         ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
470                 }
471
472                 glEnable(GL_BLEND);
473
474                 cpack(is_act ? color_act : color_base);
475
476                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
477                         glBegin(GL_LINE_STRIP);
478                         for (j = 0; j < 3; j++) {
479                                 glVertex2fv(co_ss[j]);
480                         }
481                         glEnd();
482                         cpack(0xaaaaaa);
483                         setlinestyle(3);
484                         glBegin(GL_LINE_STRIP);
485                         for (j = 0; j < 3; j++) {
486                                 glVertex2fv(co_ss[j]);
487                         }
488                         glEnd();
489                         setlinestyle(0);
490
491                         /* arc */
492                         {
493                                 float dir_tmp[3];
494                                 float co_tmp[3];
495                                 float arc_ss_coords[ARC_STEPS + 1][2];
496
497                                 float dir_a[3];
498                                 float dir_b[3];
499                                 float quat[4];
500                                 float axis[3];
501                                 float angle;
502                                 const float px_scale = (ED_view3d_pixel_size(rv3d, ruler_item->co[1]) *
503                                                         min_fff(arc_size,
504                                                                 len_v2v2(co_ss[0], co_ss[1]) / 2.0f,
505                                                                 len_v2v2(co_ss[2], co_ss[1]) / 2.0f));
506
507                                 sub_v3_v3v3(dir_a, ruler_item->co[0], ruler_item->co[1]);
508                                 sub_v3_v3v3(dir_b, ruler_item->co[2], ruler_item->co[1]);
509                                 normalize_v3(dir_a);
510                                 normalize_v3(dir_b);
511
512                                 cross_v3_v3v3(axis, dir_a, dir_b);
513                                 angle = angle_normalized_v3v3(dir_a, dir_b);
514
515                                 axis_angle_to_quat(quat, axis, angle / arc_steps);
516
517                                 copy_v3_v3(dir_tmp, dir_a);
518
519                                 glColor3ubv(color_wire);
520
521                                 for (j = 0; j <= arc_steps; j++) {
522                                         madd_v3_v3v3fl(co_tmp, ruler_item->co[1], dir_tmp, px_scale);
523                                         ED_view3d_project_float_global(ar, co_tmp, arc_ss_coords[j], V3D_PROJ_TEST_NOP);
524                                         mul_qt_v3(quat, dir_tmp);
525                                 }
526
527                                 glEnableClientState(GL_VERTEX_ARRAY);
528                                 glVertexPointer(2, GL_FLOAT, 0, arc_ss_coords);
529                                 glDrawArrays(GL_LINE_STRIP, 0, arc_steps + 1);
530                                 glDisableClientState(GL_VERTEX_ARRAY);
531                         }
532
533                         /* text */
534                         {
535                                 char numstr[256];
536                                 float numstr_size[2];
537                                 float pos[2];
538                                 const int prec = 2;  /* XXX, todo, make optional */
539
540                                 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
541
542                                 BLF_width_and_height(blf_mono_font, numstr, &numstr_size[0], &numstr_size[1]);
543
544                                 pos[0] = co_ss[1][0] + (cap_size * 2.0f);
545                                 pos[1] = co_ss[1][1] - (numstr_size[1] / 2.0f);
546
547                                 /* draw text (bg) */
548                                 glColor4ubv(color_back);
549                                 uiSetRoundBox(UI_CNR_ALL);
550                                 uiRoundBox(pos[0] - bg_margin,                  pos[1] - bg_margin,
551                                            pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
552                                            bg_radius);
553                                 /* draw text */
554                                 glColor3ubv(color_text);
555                                 BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
556                                 BLF_rotation(blf_mono_font, 0.0f);
557                                 BLF_draw(blf_mono_font, numstr, sizeof(numstr));
558                         }
559
560                         /* capping */
561                         {
562                                 float rot_90_vec_a[2];
563                                 float rot_90_vec_b[2];
564                                 float cap[2];
565
566                                 sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[1]);
567                                 rot_90_vec_a[0] = -dir_ruler[1];
568                                 rot_90_vec_a[1] =  dir_ruler[0];
569                                 normalize_v2(rot_90_vec_a);
570
571                                 sub_v2_v2v2(dir_ruler, co_ss[1], co_ss[2]);
572                                 rot_90_vec_b[0] = -dir_ruler[1];
573                                 rot_90_vec_b[1] =  dir_ruler[0];
574                                 normalize_v2(rot_90_vec_b);
575
576                                 glEnable(GL_BLEND);
577
578                                 glColor3ubv(color_wire);
579
580                                 glBegin(GL_LINES);
581
582                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, cap_size);
583                                 glVertex2fv(cap);
584                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, -cap_size);
585                                 glVertex2fv(cap);
586
587                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, cap_size);
588                                 glVertex2fv(cap);
589                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, -cap_size);
590                                 glVertex2fv(cap);
591
592                                 /* angle vertex */
593                                 glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] - cap_size);
594                                 glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] + cap_size);
595                                 glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] + cap_size);
596                                 glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] - cap_size);
597                                 glEnd();
598
599                                 glDisable(GL_BLEND);
600                         }
601                 }
602                 else {
603                         glBegin(GL_LINE_STRIP);
604                         for (j = 0; j < 3; j += 2) {
605                                 glVertex2fv(co_ss[j]);
606                         }
607                         glEnd();
608                         cpack(0xaaaaaa);
609                         setlinestyle(3);
610                         glBegin(GL_LINE_STRIP);
611                         for (j = 0; j < 3; j += 2) {
612                                 glVertex2fv(co_ss[j]);
613                         }
614                         glEnd();
615                         setlinestyle(0);
616
617                         sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]);
618
619                         /* text */
620                         {
621                                 char numstr[256];
622                                 float numstr_size[2];
623                                 const int prec = 6;  /* XXX, todo, make optional */
624                                 float pos[2];
625
626                                 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
627
628                                 BLF_width_and_height(blf_mono_font, numstr, &numstr_size[0], &numstr_size[1]);
629
630                                 mid_v2_v2v2(pos, co_ss[0], co_ss[2]);
631
632                                 /* center text */
633                                 pos[0] -= numstr_size[0] / 2.0f;
634                                 pos[1] -= numstr_size[1] / 2.0f;
635
636                                 /* draw text (bg) */
637                                 glColor4ubv(color_back);
638                                 uiSetRoundBox(UI_CNR_ALL);
639                                 uiRoundBox(pos[0] - bg_margin,                  pos[1] - bg_margin,
640                                            pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
641                                            bg_radius);
642                                 /* draw text */
643                                 glColor3ubv(color_text);
644                                 BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
645                                 BLF_draw(blf_mono_font, numstr, sizeof(numstr));
646                         }
647
648                         /* capping */
649                         {
650                                 float rot_90_vec[2] = {-dir_ruler[1], dir_ruler[0]};
651                                 float cap[2];
652
653                                 normalize_v2(rot_90_vec);
654
655                                 glEnable(GL_BLEND);
656                                 glColor3ubv(color_wire);
657
658                                 glBegin(GL_LINES);
659                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, cap_size);
660                                 glVertex2fv(cap);
661                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, -cap_size);
662                                 glVertex2fv(cap);
663
664                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, cap_size);
665                                 glVertex2fv(cap);
666                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, -cap_size);
667                                 glVertex2fv(cap);
668                                 glEnd();
669
670                                 glDisable(GL_BLEND);
671                         }
672                 }
673         }
674
675         glDisable(GL_LINE_SMOOTH);
676
677         BLF_disable(blf_mono_font, BLF_ROTATION);
678
679 #undef ARC_STEPS
680
681         /* draw snap */
682         if ((ruler_info->snap_flag & RULER_SNAP_OK) && (ruler_info->state == RULER_STATE_DRAG)) {
683                 ruler_item = ruler_item_active_get(ruler_info);
684                 if (ruler_item) {
685                         /* size from drawSnapping */
686                         const float size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
687                         float co_ss[3];
688                         ED_view3d_project_float_global(ar, ruler_item->co[ruler_item->co_index], co_ss, V3D_PROJ_TEST_NOP);
689
690                         cpack(color_act);
691                         circ(co_ss[0], co_ss[1], size * U.pixelsize);
692                 }
693         }
694
695 }
696
697 /* free, use for both cancel and finish */
698 static void view3d_ruler_end(const struct bContext *UNUSED(C), RulerInfo *ruler_info)
699 {
700         ED_region_draw_cb_exit(ruler_info->ar->type, ruler_info->draw_handle_pixel);
701 }
702
703 static void view3d_ruler_free(RulerInfo *ruler_info)
704 {
705         BLI_freelistN(&ruler_info->items);
706         MEM_freeN(ruler_info);
707 }
708
709 static void view3d_ruler_item_project(RulerInfo *ruler_info, float r_co[3],
710                                       const int xy[2])
711 {
712         ED_view3d_win_to_3d_int(ruler_info->ar, r_co, xy, r_co);
713 }
714
715 /* use for mousemove events */
716 static bool view3d_ruler_item_mousemove(bContext *C, RulerInfo *ruler_info, const int mval[2],
717                                         const bool do_thickness, const bool do_snap)
718 {
719         RulerItem *ruler_item = ruler_item_active_get(ruler_info);
720
721         ruler_info->snap_flag &= ~RULER_SNAP_OK;
722
723         if (ruler_item) {
724                 float *co = ruler_item->co[ruler_item->co_index];
725                 /* restore the initial depth */
726                 copy_v3_v3(co, ruler_info->drag_start_co);
727                 view3d_ruler_item_project(ruler_info, co, mval);
728                 if (do_thickness && ruler_item->co_index != 1) {
729                         const float mval_fl[2] = {UNPACK2(mval)};
730                         float ray_normal[3];
731                         float ray_start[3];
732                         float *co_other;
733
734                         co_other = ruler_item->co[ruler_item->co_index == 0 ? 2 : 0];
735
736                         if (ED_view3d_snap_co(C, co, ray_normal, mval_fl, true,
737                                               false, false, true))
738                         {
739                                 negate_v3(ray_normal);
740                                 /* add some bias */
741                                 madd_v3_v3v3fl(ray_start, co, ray_normal, 0.0001f);
742                                 ED_view3d_snap_ray(C, co_other,
743                                                    ray_start, ray_normal);
744                         }
745                 }
746                 else if (do_snap) {
747                         const float mval_fl[2] = {UNPACK2(mval)};
748                         View3D *v3d = CTX_wm_view3d(C);
749                         bool use_depth = (v3d->drawtype >= OB_SOLID);
750                         bool is_hit = ED_view3d_snap_co(C, co, NULL, mval_fl, use_depth,
751                                                         true, true, use_depth);
752
753                         if (is_hit) {
754                                 ruler_info->snap_flag |= RULER_SNAP_OK;
755                         }
756                 }
757                 return true;
758         }
759         else {
760                 return false;
761         }
762 }
763
764 static void view3d_ruler_header_update(ScrArea *sa)
765 {
766         const char *text = "Ctrl+LMB: Add, "
767                            "Del: Remove, "
768                            "Ctrl+Drag: Snap, "
769                            "Shift+Drag: Thickness, "
770                            "Ctrl+C: Copy Value, "
771                            "Enter: Store,  "
772                            "Esc: Cancel";
773
774         ED_area_headerprint(sa, text);
775 }
776
777 /* -------------------------------------------------------------------- */
778 /* Operator callbacks */
779
780 static int view3d_ruler_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
781 {
782         wmWindow *win = CTX_wm_window(C);
783         ScrArea *sa = CTX_wm_area(C);
784         ARegion *ar = CTX_wm_region(C);
785         RulerInfo *ruler_info;
786
787         ruler_info = MEM_callocN(sizeof(RulerInfo), "RulerInfo");
788
789         if (view3d_ruler_from_gpencil(C, ruler_info)) {
790                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
791         }
792
793         op->customdata = ruler_info;
794
795         ruler_info->win = win;
796         ruler_info->sa = sa;
797         ruler_info->ar = ar;
798         ruler_info->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ruler_info_draw_pixel,
799                                                                    ruler_info, REGION_DRAW_POST_PIXEL);
800
801         view3d_ruler_header_update(sa);
802
803         WM_cursor_modal(win, BC_CROSSCURSOR);
804         WM_event_add_modal_handler(C, op);
805
806         return OPERATOR_RUNNING_MODAL;
807 }
808
809 static int view3d_ruler_cancel(bContext *C, wmOperator *op)
810 {
811         RulerInfo *ruler_info = op->customdata;
812
813         view3d_ruler_end(C, ruler_info);
814         view3d_ruler_free(ruler_info);
815         op->customdata = NULL;
816
817         return OPERATOR_CANCELLED;
818 }
819
820 static int view3d_ruler_modal(bContext *C, wmOperator *op, const wmEvent *event)
821 {
822         bool do_draw = false;
823         int exit_code = OPERATOR_RUNNING_MODAL;
824         RulerInfo *ruler_info = op->customdata;
825         ScrArea *sa = ruler_info->sa;
826         ARegion *ar = ruler_info->ar;
827         RegionView3D *rv3d = ar->regiondata;
828
829         /* its possible to change  spaces while running the operator [#34894] */
830         if (UNLIKELY(ar != CTX_wm_region(C))) {
831                 exit_code = OPERATOR_FINISHED;
832                 goto exit;
833         }
834
835         switch (event->type) {
836                 case LEFTMOUSE:
837                         if (event->val == KM_RELEASE) {
838                                 if (ruler_info->state == RULER_STATE_DRAG) {
839                                         /* rubber-band angle removal */
840                                         RulerItem *ruler_item = ruler_item_active_get(ruler_info);
841                                         if (ruler_item && (ruler_item->co_index == 1) && (ruler_item->flag & RULERITEM_USE_ANGLE)) {
842                                                 if (!BLI_rcti_isect_pt_v(&ar->winrct, &event->x)) {
843                                                         ruler_item->flag &= ~RULERITEM_USE_ANGLE;
844                                                         do_draw = true;
845                                                 }
846                                         }
847                                         if (ruler_info->snap_flag & RULER_SNAP_OK) {
848                                                 ruler_info->snap_flag &= ~RULER_SNAP_OK;
849                                                 do_draw = true;
850                                         }
851                                         ruler_info->state = RULER_STATE_NORMAL;
852                                 }
853                         }
854                         else {
855                                 if (ruler_info->state == RULER_STATE_NORMAL) {
856
857                                         if (event->ctrl ||
858                                             /* weak - but user friendly */
859                                             (ruler_info->items.first == NULL))
860                                         {
861                                                 View3D *v3d = CTX_wm_view3d(C);
862                                                 const bool use_depth = (v3d->drawtype >= OB_SOLID);
863
864                                                 /* Create new line */
865                                                 RulerItem *ruler_item_prev = ruler_item_active_get(ruler_info);
866                                                 RulerItem *ruler_item;
867                                                 /* check if we want to drag an existing point or add a new one */
868                                                 ruler_info->state = RULER_STATE_DRAG;
869
870                                                 ruler_item = ruler_item_add(ruler_info);
871                                                 ruler_item_active_set(ruler_info, ruler_item);
872
873                                                 if (use_depth) {
874                                                         /* snap the first point added, not essential but handy */
875                                                         ruler_item->co_index = 0;
876                                                         view3d_ruler_item_mousemove(C, ruler_info, event->mval, false, true);
877                                                         copy_v3_v3(ruler_info->drag_start_co, ruler_item->co[ruler_item->co_index]);
878                                                 }
879                                                 else {
880                                                         /* initial depth either previous ruler, view offset */
881                                                         if (ruler_item_prev) {
882                                                                 copy_v3_v3(ruler_info->drag_start_co, ruler_item_prev->co[ruler_item_prev->co_index]);
883                                                         }
884                                                         else {
885                                                                 negate_v3_v3(ruler_info->drag_start_co, rv3d->ofs);
886                                                         }
887
888                                                         copy_v3_v3(ruler_item->co[0], ruler_info->drag_start_co);
889                                                         view3d_ruler_item_project(ruler_info, ruler_item->co[0], event->mval);
890                                                 }
891
892                                                 copy_v3_v3(ruler_item->co[2], ruler_item->co[0]);
893                                                 ruler_item->co_index = 2;
894
895                                                 do_draw = true;
896                                         }
897                                         else {
898                                                 float mval_fl[2] = {UNPACK2(event->mval)};
899                                                 RulerItem *ruler_item_pick;
900                                                 int co_index;
901
902                                                 /* select and drag */
903                                                 if (view3d_ruler_pick(ruler_info, mval_fl, &ruler_item_pick, &co_index)) {
904                                                         if (co_index == -1) {
905                                                                 if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
906                                                                         /* Add Center Point */
907                                                                         ruler_item_active_set(ruler_info, ruler_item_pick);
908                                                                         ruler_item_pick->flag |= RULERITEM_USE_ANGLE;
909                                                                         ruler_item_pick->co_index = 1;
910                                                                         ruler_info->state = RULER_STATE_DRAG;
911
912                                                                         /* find the factor */
913                                                                         {
914                                                                                 float co_ss[2][2];
915                                                                                 float fac;
916
917                                                                                 ED_view3d_project_float_global(ar, ruler_item_pick->co[0], co_ss[0], V3D_PROJ_TEST_NOP);
918                                                                                 ED_view3d_project_float_global(ar, ruler_item_pick->co[2], co_ss[1], V3D_PROJ_TEST_NOP);
919
920                                                                                 fac = line_point_factor_v2(mval_fl, co_ss[0], co_ss[1]);
921                                                                                 CLAMP(fac, 0.0f, 1.0f);
922
923                                                                                 interp_v3_v3v3(ruler_item_pick->co[1],
924                                                                                                ruler_item_pick->co[0],
925                                                                                                ruler_item_pick->co[2], fac);
926                                                                         }
927
928                                                                         /* update the new location */
929                                                                         view3d_ruler_item_mousemove(C, ruler_info, event->mval,
930                                                                                                     event->shift != 0, event->ctrl != 0);
931                                                                         do_draw = true;
932                                                                 }
933                                                         }
934                                                         else {
935                                                                 ruler_item_active_set(ruler_info, ruler_item_pick);
936                                                                 ruler_item_pick->co_index = co_index;
937                                                                 ruler_info->state = RULER_STATE_DRAG;
938
939                                                                 /* store the initial depth */
940                                                                 copy_v3_v3(ruler_info->drag_start_co, ruler_item_pick->co[ruler_item_pick->co_index]);
941
942                                                                 do_draw = true;
943                                                         }
944                                                 }
945                                                 else {
946                                                         exit_code = OPERATOR_PASS_THROUGH;
947                                                 }
948
949                                         }
950                                 }
951                         }
952                         break;
953                 case CKEY:
954                 {
955                         if (event->ctrl) {
956                                 RulerItem *ruler_item = ruler_item_active_get(ruler_info);
957                                 if (ruler_item) {
958                                         const int prec = 8;
959                                         char numstr[256];
960                                         Scene *scene = CTX_data_scene(C);
961                                         UnitSettings *unit = &scene->unit;
962
963                                         ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
964                                         WM_clipboard_text_set((void *) numstr, false);
965                                 }
966                         }
967                 }
968                 case RIGHTCTRLKEY:
969                 case LEFTCTRLKEY:
970                 {
971                         WM_event_add_mousemove(C);
972                         break;
973                 }
974                 case MOUSEMOVE:
975                 {
976                         if (ruler_info->state == RULER_STATE_DRAG) {
977                                 if (view3d_ruler_item_mousemove(C, ruler_info, event->mval,
978                                                                 event->shift != 0, event->ctrl != 0))
979                                 {
980                                         do_draw = true;
981                                 }
982                         }
983                         break;
984                 }
985                 case ESCKEY:
986                 {
987                         do_draw = true;
988                         exit_code = OPERATOR_CANCELLED;
989                         break;
990                 }
991                 case RETKEY:
992                 {
993                         view3d_ruler_to_gpencil(C, ruler_info);
994                         do_draw = true;
995                         exit_code = OPERATOR_FINISHED;
996                         break;
997                 }
998                 case DELKEY:
999                 {
1000                         if (event->val == KM_PRESS) {
1001                                 if (ruler_info->state == RULER_STATE_NORMAL) {
1002                                         RulerItem *ruler_item = ruler_item_active_get(ruler_info);
1003                                         if (ruler_item) {
1004                                                 RulerItem *ruler_item_other = ruler_item->prev ? ruler_item->prev : ruler_item->next;
1005                                                 ruler_item_remove(ruler_info, ruler_item);
1006                                                 ruler_item_active_set(ruler_info, ruler_item_other);
1007                                                 do_draw = true;
1008                                         }
1009                                 }
1010                         }
1011                         break;
1012                 }
1013                 default:
1014                         exit_code = OPERATOR_PASS_THROUGH;
1015                         break;
1016
1017         }
1018
1019         if (do_draw) {
1020                 view3d_ruler_header_update(sa);
1021
1022                 /* all 3d views draw rulers */
1023                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
1024         }
1025
1026 exit:
1027         if (ELEM(exit_code, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
1028                 WM_cursor_restore(ruler_info->win);
1029
1030                 view3d_ruler_end(C, ruler_info);
1031                 view3d_ruler_free(ruler_info);
1032                 op->customdata = NULL;
1033
1034                 ED_area_headerprint(sa, NULL);
1035         }
1036
1037         return exit_code;
1038 }
1039
1040 void VIEW3D_OT_ruler(wmOperatorType *ot)
1041 {
1042         /* identifiers */
1043         ot->name = "Ruler/Protractor";
1044         ot->description = "Interactive ruler";
1045         ot->idname = "VIEW3D_OT_ruler";
1046
1047         /* api callbacks */
1048         ot->invoke = view3d_ruler_invoke;
1049         ot->cancel = view3d_ruler_cancel;
1050         ot->modal = view3d_ruler_modal;
1051         ot->poll = ED_operator_view3d_active;
1052
1053         /* flags */
1054         ot->flag = 0;
1055 }