ruler/protractor for 3d viewport (apart of 3d printing features). work-in-progress.
authorCampbell Barton <ideasman42@gmail.com>
Thu, 7 Mar 2013 11:53:11 +0000 (11:53 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Thu, 7 Mar 2013 11:53:11 +0000 (11:53 +0000)
- activate from spacebar search (3D Ruler)
- ctrl-click adds new rulers
- clicking in the middle of a ruler, turns into protractor, dragging out of view snaps back to ruler.

source/blender/editors/space_view3d/CMakeLists.txt
source/blender/editors/space_view3d/view3d_intern.h
source/blender/editors/space_view3d/view3d_ops.c
source/blender/editors/space_view3d/view3d_ruler.c [new file with mode: 0644]

index 35dd88c..1c5f4a6 100644 (file)
@@ -54,6 +54,7 @@ set(SRC
        view3d_iterators.c
        view3d_ops.c
        view3d_project.c
+       view3d_ruler.c
        view3d_select.c
        view3d_snap.c
        view3d_toolbar.c
index d4cfa90..26ed8e1 100644 (file)
@@ -108,6 +108,9 @@ float ndof_to_axis_angle(const struct wmNDOFMotionData *ndof, float axis[3]);
 void view3d_keymap(struct wmKeyConfig *keyconf);
 void VIEW3D_OT_fly(struct wmOperatorType *ot);
 
+/* view3d_ruler.c */
+void VIEW3D_OT_ruler(struct wmOperatorType *ot);
+
 /* drawanim.c */
 void draw_motion_paths_init(View3D *v3d, struct ARegion *ar);
 void draw_motion_path_instance(Scene *scene,
index bf1c540..e567ebd 100644 (file)
@@ -166,6 +166,7 @@ void view3d_operatortypes(void)
        WM_operatortype_append(VIEW3D_OT_localview);
        WM_operatortype_append(VIEW3D_OT_game_start);
        WM_operatortype_append(VIEW3D_OT_fly);
+       WM_operatortype_append(VIEW3D_OT_ruler);
        WM_operatortype_append(VIEW3D_OT_layers);
        WM_operatortype_append(VIEW3D_OT_copybuffer);
        WM_operatortype_append(VIEW3D_OT_pastebuffer);
diff --git a/source/blender/editors/space_view3d/view3d_ruler.c b/source/blender/editors/space_view3d/view3d_ruler.c
new file mode 100644 (file)
index 0000000..e2e6561
--- /dev/null
@@ -0,0 +1,704 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Campbell Barton
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/space_view3d/view3d_ruler.c
+ *  \ingroup spview3d
+ */
+
+/* defines VIEW3D_OT_ruler modal operator */
+
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_blenlib.h"
+
+#include "BKE_context.h"
+#include "BKE_unit.h"
+
+#include "BIF_gl.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_screen.h"
+#include "ED_space_api.h"
+
+#include "BLF_api.h"
+#include "BIF_glutil.h"
+
+#include "UI_resources.h"
+#include "UI_interface.h"
+
+#include "view3d_intern.h"  /* own include */
+
+/* -------------------------------------------------------------------- */
+/* Ruler Item (we can have many) */
+enum {
+       RULERITEM_USE_ANGLE = (1 << 0),  /* use protractor */
+       RULERITEM_USE_RAYCAST = (1 << 1)
+};
+
+enum {
+       RULERITEM_DIRECTION_IN = 0,
+       RULERITEM_DIRECTION_OUT
+};
+
+#define RULER_PICK_DIST 75.0f
+#define RULER_PICK_DIST_SQ (RULER_PICK_DIST * RULER_PICK_DIST)
+
+typedef struct RulerItem {
+       struct RulerItem *next, *prev;
+
+       /* worldspace coords, middle being optional */
+       float co[3][3];
+
+       /* selected coord */
+       char  co_index; /* 0 -> 2*/
+
+       int   flag;
+       int   raycast_dir;  /* RULER_DIRECTION_* */
+} RulerItem;
+
+enum {
+       RULER_STATE_NORMAL = 0,
+       RULER_STATE_DRAG
+};
+
+
+/* -------------------------------------------------------------------- */
+/* Ruler Info (one per session) */
+
+typedef struct RulerInfo {
+       ListBase items;
+       int      item_active;
+       int flag;
+       int snap_flag;
+       int state;
+
+       /* --- */
+       ARegion *ar;
+       void *draw_handle_pixel;
+} RulerInfo;
+
+/* -------------------------------------------------------------------- */
+/* local functions */
+static RulerItem *ruler_item_add(RulerInfo *ruler_info)
+{
+       RulerItem *ruler_item = MEM_callocN(sizeof(RulerItem), "RulerItem");
+       BLI_addtail(&ruler_info->items, ruler_item);
+       return ruler_item;
+}
+
+#if 0
+static void ruler_item_remove(RulerInfo *ruler_info, RulerItem *ruler_item)
+{
+       BLI_remlink(&ruler_info->items, ruler_item);
+       MEM_freeN(ruler_item);
+}
+#endif
+
+static RulerItem *ruler_item_active_get(RulerInfo *ruler_info)
+{
+       return BLI_findlink(&ruler_info->items, ruler_info->item_active);
+}
+
+static void ruler_item_active_set(RulerInfo *ruler_info, RulerItem *ruler_item)
+{
+       ruler_info->item_active = BLI_findindex(&ruler_info->items, ruler_item);
+}
+
+static bool view3d_ruler_pick(RulerInfo *ruler_info, const float mval[2],
+                              RulerItem **r_ruler_item, int *r_co_index)
+{
+       ARegion *ar = ruler_info->ar;
+       RulerItem *ruler_item;
+
+       float dist_best = RULER_PICK_DIST_SQ;
+       RulerItem *ruler_item_best = NULL;
+       int co_index_best = -1;
+
+       for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
+               float co_ss[3][2];
+               float dist;
+               int j;
+
+               /* should these be checked? - ok for now not to */
+               for (j = 0; j < 3; j++) {
+                       ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
+               }
+
+               if (ruler_item->flag & RULERITEM_USE_ANGLE) {
+                       dist = min_ff(dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[1]),
+                                     dist_squared_to_line_segment_v2(mval, co_ss[1], co_ss[2]));
+                       if (dist < dist_best) {
+                               dist_best = dist;
+                               ruler_item_best = ruler_item;
+
+                               {
+                                       float dist_points[3] = {len_squared_v2v2(co_ss[0], mval),
+                                                               len_squared_v2v2(co_ss[1], mval),
+                                                               len_squared_v2v2(co_ss[2], mval)};
+                                       if (min_fff(UNPACK3(dist_points)) < RULER_PICK_DIST_SQ) {
+                                               co_index_best = min_axis_v3(dist_points);
+                                       }
+                                       else {
+                                               co_index_best = -1;
+                                       }
+                               }
+                       }
+               }
+               else {
+                       dist = dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[2]);
+                       if (dist < dist_best) {
+                               dist_best = dist;
+                               ruler_item_best = ruler_item;
+
+                               {
+                                       float dist_points[2] = {len_squared_v2v2(co_ss[0], mval),
+                                                               len_squared_v2v2(co_ss[2], mval)};
+                                       if (min_ff(UNPACK2(dist_points)) < RULER_PICK_DIST_SQ) {
+                                               co_index_best = (dist_points[0] < dist_points[1]) ? 0 : 2;
+                                       }
+                                       else {
+                                               co_index_best = -1;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (ruler_item_best) {
+               *r_ruler_item = ruler_item_best;
+               *r_co_index = co_index_best;
+               return true;
+       }
+       else {
+               *r_ruler_item = NULL;
+               *r_co_index = -1;
+               return false;
+       }
+}
+
+/* -------------------------------------------------------------------- */
+/* local callbacks */
+
+static void ruler_info_draw_pixel(const struct bContext *C, ARegion *ar, void *arg)
+{
+       Scene *scene = CTX_data_scene(C);
+       UnitSettings *unit = &scene->unit;
+       const int do_split = unit->flag & USER_UNIT_OPT_SPLIT;
+       RulerItem *ruler_item;
+       RulerInfo *ruler_info = arg;
+       RegionView3D *rv3d = ruler_info->ar->regiondata;
+//     ARegion *ar = ruler_info->ar;
+       const float cap_size = 4.0f;
+       const float bg_margin = 4.0f * U.pixelsize;
+       const float bg_radius = 4.0f * U.pixelsize;
+       const float arc_size = 64.0f * U.pixelsize;
+#define ARC_STEPS 24
+       const int arc_steps = ARC_STEPS;
+       int i;
+       //unsigned int color_act = 0x666600;
+       unsigned int color_act = 0xffffff;
+       unsigned int color_base = 0x0;
+       unsigned char color_back[4] = {0xff, 0xff, 0xff, 0x80};
+       unsigned char color_text[3];
+       unsigned char color_wire[3];
+
+       /* anti-aliased lines for more consistent appearance */
+       glEnable(GL_LINE_SMOOTH);
+
+       BLF_enable(blf_mono_font, BLF_ROTATION);
+       BLF_size(blf_mono_font, 14 * U.pixelsize, U.dpi);
+
+       UI_GetThemeColor3ubv(TH_TEXT, color_text);
+       UI_GetThemeColor3ubv(TH_WIRE, color_wire);
+
+       for (ruler_item = ruler_info->items.first, i = 0; ruler_item; ruler_item = ruler_item->next, i++) {
+               const bool is_act = (i == ruler_info->item_active);
+               float dir_ruler[2];
+               float co_ss[3][2];
+               int j;
+
+               /* should these be checked? - ok for now not to */
+               for (j = 0; j < 3; j++) {
+                       ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
+               }
+
+               glEnable(GL_BLEND);
+
+               cpack(is_act ? color_act : color_base);
+
+               if (ruler_item->flag & RULERITEM_USE_ANGLE) {
+                       const float ruler_angle = angle_v3v3v3(ruler_item->co[0],
+                                                              ruler_item->co[1],
+                                                              ruler_item->co[2]);
+                       glBegin(GL_LINE_STRIP);
+                       for (j = 0; j < 3; j++) {
+                               glVertex2fv(co_ss[j]);
+                       }
+                       glEnd();
+                       cpack(0xaaaaaa);
+                       setlinestyle(3);
+                       glBegin(GL_LINE_STRIP);
+                       for (j = 0; j < 3; j++) {
+                               glVertex2fv(co_ss[j]);
+                       }
+                       glEnd();
+                       setlinestyle(0);
+
+                       /* arc */
+                       {
+                               float dir_tmp[3];
+                               float co_tmp[3];
+                               float arc_ss_coords[ARC_STEPS + 1][2];
+
+                               float dir_a[3];
+                               float dir_b[3];
+                               float quat[4];
+                               float axis[3];
+                               float angle;
+                               int j;
+                               const float px_scale = ED_view3d_pixel_size(rv3d, ruler_item->co[1]) * arc_size;
+
+                               sub_v3_v3v3(dir_a, ruler_item->co[0], ruler_item->co[1]);
+                               sub_v3_v3v3(dir_b, ruler_item->co[2], ruler_item->co[1]);
+                               normalize_v3(dir_a);
+                               normalize_v3(dir_b);
+
+                               cross_v3_v3v3(axis, dir_a, dir_b);
+                               angle = angle_normalized_v3v3(dir_a, dir_b);
+
+                               axis_angle_to_quat(quat, axis, angle / arc_steps);
+
+                               copy_v3_v3(dir_tmp, dir_a);
+
+                               glColor3ubv(color_wire);
+
+                               for(j = 0; j <= arc_steps; j++) {
+                                       madd_v3_v3v3fl(co_tmp, ruler_item->co[1], dir_tmp, px_scale);
+                                       ED_view3d_project_float_global(ar, co_tmp, arc_ss_coords[j], V3D_PROJ_TEST_NOP);
+                                       mul_qt_v3(quat, dir_tmp);
+                               }
+
+                               glEnableClientState(GL_VERTEX_ARRAY);
+                               glVertexPointer(2, GL_FLOAT, 0, arc_ss_coords);
+                               glDrawArrays(GL_LINE_STRIP, 0, arc_steps + 1);
+                               glDisableClientState(GL_VERTEX_ARRAY);
+                       }
+
+                       /* text */
+                       {
+                               char numstr[256];
+                               float numstr_size[2];
+                               float pos[2];
+                               const int prec = 2;  /* XXX, todo, make optional */
+
+                               if (unit->system == USER_UNIT_NONE) {
+                                       BLI_snprintf(numstr, sizeof(numstr), "%.*f°", prec, RAD2DEGF(ruler_angle));
+                               }
+                               else {
+                                       bUnit_AsString(numstr, sizeof(numstr),
+                                                      (double)ruler_angle,
+                                                      prec, unit->system, B_UNIT_ROTATION, do_split, false);
+                               }
+                               BLF_width_and_height(blf_mono_font, numstr, &numstr_size[0], &numstr_size[1]);
+
+                               pos[0] = co_ss[1][0] + (cap_size * 2.0f);
+                               pos[1] = co_ss[1][1] - (numstr_size[1] / 2.0f);
+
+                               /* draw text (bg) */
+                               glColor4ubv(color_back);
+                               uiSetRoundBox(UI_CNR_ALL);
+                               uiRoundBox(pos[0] - bg_margin, pos[1] - bg_margin,
+                                          pos[0] + numstr_size[0] + bg_margin, pos[1] + numstr_size[1] + bg_margin,
+                                          bg_radius);
+                               /* draw text */
+                               glColor3ubv(color_text);
+                               BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
+                               BLF_rotation(blf_mono_font, 0.0f);
+                               BLF_draw(blf_mono_font, numstr, sizeof(numstr));
+                       }
+
+                       /* capping */
+                       {
+                               float rot_90_vec_a[2];
+                               float rot_90_vec_b[2];
+                               float cap[2];
+
+                               sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[1]);
+                               rot_90_vec_a[0] = -dir_ruler[1];
+                               rot_90_vec_a[1] =  dir_ruler[0];
+                               normalize_v2(rot_90_vec_a);
+
+                               sub_v2_v2v2(dir_ruler, co_ss[1], co_ss[2]);
+                               rot_90_vec_b[0] = -dir_ruler[1];
+                               rot_90_vec_b[1] =  dir_ruler[0];
+                               normalize_v2(rot_90_vec_b);
+
+                               glEnable(GL_BLEND);
+
+                               glColor3ubv(color_wire);
+
+                               glBegin(GL_LINES);
+
+                               madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, cap_size);
+                               glVertex2fv(cap);
+                               madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, -cap_size);
+                               glVertex2fv(cap);
+
+                               madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, cap_size);
+                               glVertex2fv(cap);
+                               madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, -cap_size);
+                               glVertex2fv(cap);
+
+                               /* angle vertex */
+                               glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] - cap_size);
+                               glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] + cap_size);
+                               glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] + cap_size);
+                               glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] - cap_size);
+                               glEnd();
+
+                               glDisable(GL_BLEND);
+                       }
+               }
+               else {
+                       const float ruler_len = len_v3v3(ruler_item->co[0], ruler_item->co[2]);
+                       glBegin(GL_LINE_STRIP);
+                       for (j = 0; j < 3; j += 2) {
+                               glVertex2fv(co_ss[j]);
+                       }
+                       glEnd();
+                       cpack(0xaaaaaa);
+                       setlinestyle(3);
+                       glBegin(GL_LINE_STRIP);
+                       for (j = 0; j < 3; j += 2) {
+                               glVertex2fv(co_ss[j]);
+                       }
+                       glEnd();
+                       setlinestyle(0);
+
+                       sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]);
+
+                       /* text */
+                       {
+                               char numstr[256];
+                               float numstr_size[2];
+                               const int prec = 6;  /* XXX, todo, make optional */
+                               const float dir_default_x[2] = {1, 0};
+                               float pos[2];
+                               float numstr_angle;
+                               bool flip_text;
+
+
+                               /* angle for text */
+                               numstr_angle = angle_signed_v2v2(dir_ruler, dir_default_x);
+
+                               /* keep text upright */
+                               if (numstr_angle >= (float)(M_PI / 2.0)) {
+                                       numstr_angle -= (float)M_PI;
+                                       flip_text = true;
+                               }
+                               else if (numstr_angle <= -(float)(M_PI / 2.0)) {
+                                       numstr_angle += (float)M_PI;
+                                       flip_text = true;
+                               }
+                               else {
+                                       flip_text = false;
+                               }
+
+                               if (unit->system == USER_UNIT_NONE) {
+                                       BLI_snprintf(numstr, sizeof(numstr), "%.*f", prec, ruler_len);
+                               }
+                               else {
+                                       bUnit_AsString(numstr, sizeof(numstr),
+                                                      (double)(ruler_len * unit->scale_length),
+                                                      prec, unit->system, B_UNIT_LENGTH, do_split, false);
+                               }
+                               BLF_width_and_height(blf_mono_font, numstr, &numstr_size[0], &numstr_size[1]);
+
+                               mid_v2_v2v2(pos, co_ss[0], co_ss[2]);
+
+                               /* center text */
+                               normalize_v2(dir_ruler);
+                               madd_v2_v2fl(pos, dir_ruler, numstr_size[0] / (flip_text ? 2.0f : -2.0f));
+
+                               /* draw text (bg) */
+                               glTranslatef(pos[0], pos[1], 0.0f);
+                               glRotatef(RAD2DEGF(numstr_angle), 0.0f, 0.0f, 1.0f);
+                               glColor4ubv(color_back);
+                               uiSetRoundBox(UI_CNR_ALL);
+                               uiRoundBox(-bg_margin, -bg_margin,
+                                          numstr_size[0] + bg_margin, numstr_size[1] + bg_margin,
+                                          bg_radius);
+                               glRotatef(-RAD2DEGF(numstr_angle), 0.0f, 0.0f, 1.0f);
+                               glTranslatef(-pos[0], -pos[1], 0.0f);
+                               /* draw text */
+                               glColor3ubv(color_text);
+                               BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
+                               BLF_rotation(blf_mono_font, numstr_angle);
+                               BLF_draw(blf_mono_font, numstr, sizeof(numstr));
+                       }
+
+                       /* capping */
+                       {
+                               float rot_90_vec[2] = {-dir_ruler[1], dir_ruler[0]};
+                               float cap[2];
+
+                               normalize_v2(rot_90_vec);
+
+                               glEnable(GL_BLEND);
+                               glColor3ubv(color_wire);
+
+                               glBegin(GL_LINES);
+                               madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, cap_size);
+                               glVertex2fv(cap);
+                               madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, -cap_size);
+                               glVertex2fv(cap);
+
+                               madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, cap_size);
+                               glVertex2fv(cap);
+                               madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, -cap_size);
+                               glVertex2fv(cap);
+                               glEnd();
+
+                               glDisable(GL_BLEND);
+                       }
+               }
+       }
+
+       glDisable(GL_LINE_SMOOTH);
+
+       BLF_disable(blf_mono_font, BLF_ROTATION);
+
+#undef ARC_STEPS
+}
+
+/* free, use for both cancel and finish */
+static void view3d_ruler_end(const struct bContext *UNUSED(C), RulerInfo *ruler_info)
+{
+       ED_region_draw_cb_exit(ruler_info->ar->type, ruler_info->draw_handle_pixel);
+}
+
+static void view3d_ruler_free(RulerInfo *ruler_info)
+{
+       BLI_freelistN(&ruler_info->items);
+       MEM_freeN(ruler_info);
+}
+
+static void view3d_ruler_item_project(bContext *C, RulerInfo *UNUSED(ruler_info), float r_co[3],
+                                      const int xy[2], const float ref[3])
+{
+       copy_v3_v3(r_co, ref);
+       ED_view3d_cursor3d_position(C, r_co, xy);
+}
+
+/* use for mousemove events */
+static bool view3d_ruler_item_mousemove(bContext *C, RulerInfo *ruler_info, const wmEvent *event, const bool use_ofs)
+{
+       RulerItem *ruler_item = ruler_item_active_get(ruler_info);
+
+       if (ruler_item) {
+               RegionView3D *rv3d = ruler_info->ar->regiondata;
+               float _ref_ofs[3];
+               float const *ref;
+
+               if (use_ofs) {
+                       negate_v3_v3(_ref_ofs, rv3d->ofs);
+                       ref = _ref_ofs;
+               }
+               else {
+                       ref = ruler_item->co[ruler_item->co_index];
+               }
+
+               view3d_ruler_item_project(C, ruler_info, ruler_item->co[ruler_item->co_index], event->mval, ref);
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/* -------------------------------------------------------------------- */
+/* Operator callbacks */
+
+static int view3d_ruler_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+       ARegion *ar = CTX_wm_region(C);
+//     RegionView3D *rv3d = CTX_wm_region_view3d(C);
+       RulerInfo *ruler_info;
+       (void)event;
+
+       ruler_info = MEM_callocN(sizeof(RulerInfo), "RulerInfo");
+
+       op->customdata = ruler_info;
+
+       ruler_info->ar = ar;
+       ruler_info->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ruler_info_draw_pixel, ruler_info, REGION_DRAW_POST_PIXEL);
+
+       WM_event_add_modal_handler(C, op);
+
+       return OPERATOR_RUNNING_MODAL;
+}
+
+static int view3d_ruler_cancel(bContext *C, wmOperator *op)
+{
+       RulerInfo *ruler_info = op->customdata;
+
+       view3d_ruler_end(C, ruler_info);
+       view3d_ruler_free(ruler_info);
+       op->customdata = NULL;
+
+       return OPERATOR_CANCELLED;
+}
+
+static int view3d_ruler_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+       bool do_draw = false;
+       int exit_code = OPERATOR_RUNNING_MODAL;
+       RulerInfo *ruler_info = op->customdata;
+       RegionView3D *rv3d = ruler_info->ar->regiondata;
+
+       (void)C;
+
+       switch (event->type) {
+               case LEFTMOUSE:
+                       if (event->val == KM_RELEASE) {
+                               if (ruler_info->state == RULER_STATE_DRAG) {
+                                       /* rubber-band angle removal */
+                                       RulerItem *ruler_item = ruler_item_active_get(ruler_info);
+                                       if (ruler_item && (ruler_item->co_index == 1) && (ruler_item->flag & RULERITEM_USE_ANGLE)) {
+                                               if (!BLI_rcti_isect_pt_v(&ruler_info->ar->winrct, &event->x)) {
+                                                       ruler_item->flag &= ~RULERITEM_USE_ANGLE;
+                                                       do_draw = true;
+                                               }
+                                       }
+                                       ruler_info->state = RULER_STATE_NORMAL;
+                               }
+                       }
+                       else {
+                               if (ruler_info->state == RULER_STATE_NORMAL) {
+
+                                       if (event->ctrl) {
+                                               /* Create new line */
+                                               const float ref[3] = {UNPACK3OP(rv3d->ofs, -)};
+                                               RulerItem *ruler_item;
+                                               /* check if we want to drag an existing point or add a new one */
+                                               ruler_info->state = RULER_STATE_DRAG;
+
+                                               ruler_item = ruler_item_add(ruler_info);
+                                               ruler_item_active_set(ruler_info, ruler_item);
+                                               ruler_item->co_index = 2;
+
+                                               view3d_ruler_item_project(C, ruler_info, ruler_item->co[0], event->mval, ref); // XXX
+                                               copy_v3_v3(ruler_item->co[2], ruler_item->co[0]);
+
+                                               do_draw = true;
+                                       }
+                                       else {
+                                               float mval_fl[2] = {UNPACK2(event->mval)};
+                                               RulerItem *ruler_item_pick;
+                                               int co_index;
+
+                                               /* select and drag */
+                                               if (view3d_ruler_pick(ruler_info, mval_fl, &ruler_item_pick, &co_index)) {
+                                                       if (co_index == -1) {
+                                                               if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
+                                                                       /* Add Center Point */
+                                                                       ruler_item_active_set(ruler_info, ruler_item_pick);
+                                                                       ruler_item_pick->flag |= RULERITEM_USE_ANGLE;
+                                                                       ruler_item_pick->co_index = 1;
+                                                                       ruler_info->state = RULER_STATE_DRAG;
+
+                                                                       /* update the new location */
+                                                                       view3d_ruler_item_mousemove(C, ruler_info, event, true);
+                                                                       do_draw = true;
+                                                               }
+                                                       }
+                                                       else {
+                                                               ruler_item_active_set(ruler_info, ruler_item_pick);
+                                                               ruler_item_pick->co_index = co_index;
+                                                               ruler_info->state = RULER_STATE_DRAG;
+                                                               do_draw = true;
+                                                       }
+                                               }
+                                               else {
+                                                       exit_code = OPERATOR_PASS_THROUGH;
+                                               }
+
+                                       }
+                               }
+                       }
+                       break;
+               case MOUSEMOVE:
+               {
+                       if (ruler_info->state == RULER_STATE_DRAG) {
+                               if (view3d_ruler_item_mousemove(C, ruler_info, event, false)) {
+                                       do_draw = true;
+                               }
+                       }
+                       break;
+               }
+
+               case ESCKEY:
+               {
+                       do_draw = true;
+                       exit_code = OPERATOR_CANCELLED;
+                       break;
+               }
+               default:
+                       exit_code = OPERATOR_PASS_THROUGH;
+                       break;
+
+       }
+
+       if (do_draw) {
+               ED_region_tag_redraw(ruler_info->ar);
+       }
+
+       if (ELEM(exit_code, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
+               view3d_ruler_end(C, ruler_info);
+               view3d_ruler_free(ruler_info);
+               op->customdata = NULL;
+       }
+
+       return exit_code;
+}
+
+void VIEW3D_OT_ruler(wmOperatorType *ot)
+{
+       /* identifiers */
+       ot->name = "3D Ruler";
+       ot->description = "Interactive ruler";
+       ot->idname = "VIEW3D_OT_ruler";
+
+       /* api callbacks */
+       ot->invoke = view3d_ruler_invoke;
+       ot->cancel = view3d_ruler_cancel;
+       ot->modal = view3d_ruler_modal;
+       ot->poll = ED_operator_view3d_active;
+
+       /* flags */
+       ot->flag = OPTYPE_BLOCKING;
+}