Merge branch 'master' into blender2.8
authorCampbell Barton <ideasman42@gmail.com>
Tue, 13 Nov 2018 22:50:04 +0000 (09:50 +1100)
committerCampbell Barton <ideasman42@gmail.com>
Tue, 13 Nov 2018 22:54:55 +0000 (09:54 +1100)
18 files changed:
1  2 
source/blender/editors/interface/interface_layout.c
source/blender/editors/interface/view2d_ops.c
source/blender/editors/mask/mask_shapekey.c
source/blender/editors/mesh/editmesh_polybuild.c
source/blender/editors/mesh/editmesh_tools.c
source/blender/editors/object/object_relations.c
source/blender/editors/space_file/file_ops.c
source/blender/editors/space_outliner/outliner_edit.c
source/blender/editors/space_outliner/outliner_tools.c
source/blender/editors/space_sequencer/sequencer_edit.c
source/blender/editors/space_text/text_ops.c
source/blender/editors/space_view3d/view3d_buttons.c
source/blender/editors/space_view3d/view3d_gizmo_ruler.c
source/blender/editors/space_view3d/view3d_view.c
source/blender/makesrna/intern/rna_ui.c
source/blender/makesrna/intern/rna_wm.c
source/blender/windowmanager/intern/wm_menu_type.c
source/blender/windowmanager/intern/wm_operator_type.c

index e608058dd995c3521cece3c5ed92d39c7e690e73,922b5779c9f467cfecd5fd7a83c6e03abb93b3ee..76296b128986a2926aab334cff14b6951048bbad
@@@ -2259,79 -1979,11 +2259,81 @@@ void uiItemM(uiLayout *layout, const ch
        if (layout->root->type == UI_LAYOUT_MENU && !icon)
                icon = ICON_BLANK1;
  
-       ui_item_menu(layout, name, icon, ui_item_menutype_func, mt, NULL, TIP_(mt->description), false);
+       ui_item_menu(
+               layout, name, icon, ui_item_menutype_func, mt, NULL,
+               mt->description ? TIP_(mt->description) : "", false);
  }
  
 +/* popover */
 +void uiItemPopoverPanel_ptr(uiLayout *layout, bContext *C, PanelType *pt, const char *name, int icon)
 +{
 +      if (!name) {
 +              name = CTX_IFACE_(pt->translation_context, pt->label);
 +      }
 +
 +      if (layout->root->type == UI_LAYOUT_MENU && !icon) {
 +              icon = ICON_BLANK1;
 +      }
 +
 +      const bool ok = (pt->poll == NULL) || pt->poll(C, pt);
 +      if (ok && (pt->draw_header != NULL)) {
 +              layout = uiLayoutRow(layout, true);
 +              Panel panel = {
 +                      .type = pt,
 +                      .layout = layout,
 +                      .flag = PNL_POPOVER,
 +              };
 +              pt->draw_header(C, &panel);
 +      }
 +      uiBut *but = ui_item_menu(layout, name, icon, ui_item_paneltype_func, pt, NULL, NULL, true);
 +      but->type = UI_BTYPE_POPOVER;
 +      if (!ok) {
 +              but->flag |= UI_BUT_DISABLED;
 +      }
 +}
 +
 +void uiItemPopoverPanel(
 +        uiLayout *layout, bContext *C,
 +        const char *panel_type, const char *name, int icon)
 +{
 +      PanelType *pt = WM_paneltype_find(panel_type, true);
 +      if (pt == NULL) {
 +              RNA_warning("Panel type not found '%s'", panel_type);
 +              return;
 +      }
 +      uiItemPopoverPanel_ptr(layout, C, pt, name, icon);
 +}
 +
 +void uiItemPopoverPanelFromGroup(
 +        uiLayout *layout, bContext *C,
 +        int space_id, int region_id, const char *context, const char *category)
 +{
 +      SpaceType *st = BKE_spacetype_from_id(space_id);
 +      if (st == NULL) {
 +              RNA_warning("space type not found %d", space_id);
 +              return;
 +      }
 +      ARegionType *art = BKE_regiontype_from_id(st, region_id);
 +      if (art == NULL) {
 +              RNA_warning("region type not found %d", region_id);
 +              return;
 +      }
 +
 +      for (PanelType *pt = art->paneltypes.first; pt; pt = pt->next) {
 +              /* Causes too many panels, check context. */
 +              if (pt->parent_id[0] == '\0') {
 +                      if (/* (*context == '\0') || */ STREQ(pt->context, context)) {
 +                              if ((*category == '\0') || STREQ(pt->category, category)) {
 +                                      if (pt->poll == NULL || pt->poll(C, pt)) {
 +                                              uiItemPopoverPanel_ptr(layout, C, pt, NULL, ICON_NONE);
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +}
 +
 +
  /* label item */
  static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon)
  {
index bf927478187a0083c971cd5dfae2fdf978b726e1,0000000000000000000000000000000000000000..882ee3092b4a7059866ea738b6e01cb4b893ae6c
mode 100644,000000..100644
--- /dev/null
@@@ -1,499 -1,0 +1,496 @@@
-       ot->description = "";
 +/*
 + * ***** 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.
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/editors/mesh/editmesh_polybuild.c
 + *  \ingroup edmesh
 + *
 + * Tools to implement polygon building tool,
 + * an experimental tool for quickly constructing/manipulating faces.
 + */
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "DNA_object_types.h"
 +
 +#include "BLI_math.h"
 +
 +#include "BKE_context.h"
 +#include "BKE_report.h"
 +#include "BKE_editmesh.h"
 +#include "BKE_mesh.h"
 +#include "BKE_layer.h"
 +
 +#include "WM_types.h"
 +
 +#include "ED_object.h"
 +#include "ED_mesh.h"
 +#include "ED_scene.h"
 +#include "ED_screen.h"
 +#include "ED_transform.h"
 +#include "ED_view3d.h"
 +
 +#include "bmesh.h"
 +
 +#include "mesh_intern.h"  /* own include */
 +
 +#include "RNA_access.h"
 +#include "RNA_define.h"
 +
 +#include "WM_api.h"
 +
 +#include "DEG_depsgraph.h"
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Local Utilities
 + * \{ */
 +
 +static void edbm_selectmode_ensure(Scene *scene, BMEditMesh *em, short selectmode)
 +{
 +      if ((scene->toolsettings->selectmode & selectmode) == 0) {
 +              scene->toolsettings->selectmode |= selectmode;
 +              em->selectmode = scene->toolsettings->selectmode;
 +              EDBM_selectmode_set(em);
 +      }
 +}
 +
 +/* Could make public, for now just keep here. */
 +static void edbm_flag_disable_all_multi(ViewLayer *view_layer, const char hflag)
 +{
 +      uint objects_len = 0;
 +      Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
 +      for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
 +              Object *ob_iter = objects[ob_index];
 +              BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter);
 +              BMesh *bm_iter = em_iter->bm;
 +              if (bm_iter->totvertsel) {
 +                      EDBM_flag_disable_all(em_iter, hflag);
 +                      DEG_id_tag_update(ob_iter->data, DEG_TAG_SELECT_UPDATE);
 +              }
 +      }
 +      MEM_freeN(objects);
 +}
 +
 +/* When accessed as a tool, get the active edge from the preselection gizmo. */
 +static bool edbm_preselect_or_active(
 +        bContext *C,
 +        Base **r_base,
 +        BMElem **r_ele)
 +{
 +      ViewLayer *view_layer = CTX_data_view_layer(C);
 +      ARegion *ar = CTX_wm_region(C);
 +      wmGizmoMap *gzmap = ar->gizmo_map;
 +      wmGizmoGroup *gzgroup = gzmap ? WM_gizmomap_group_find(gzmap, "VIEW3D_GGT_mesh_preselect_elem") : NULL;
 +      if (gzgroup != NULL) {
 +              wmGizmo *gz = gzgroup->gizmos.first;
 +              const int object_index = RNA_int_get(gz->ptr, "object_index");
 +
 +              /* weak, allocate an array just to access the index. */
 +              Base *base = NULL;
 +              Object *obedit = NULL;
 +              {
 +                      uint bases_len;
 +                      Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(view_layer, &bases_len);
 +                      if (object_index < bases_len) {
 +                              base = bases[object_index];
 +                              obedit = base->object;
 +                      }
 +                      MEM_freeN(bases);
 +              }
 +
 +              *r_base = base;
 +              *r_ele = NULL;
 +
 +              if (obedit) {
 +                      BMEditMesh *em = BKE_editmesh_from_object(obedit);
 +                      BMesh *bm = em->bm;
 +                      const int vert_index = RNA_int_get(gz->ptr, "vert_index");
 +                      const int edge_index = RNA_int_get(gz->ptr, "edge_index");
 +                      const int face_index = RNA_int_get(gz->ptr, "face_index");
 +                      if (vert_index != -1) {
 +                              *r_ele = (BMElem *)BM_vert_at_index_find(bm, vert_index);
 +                      }
 +                      else if (edge_index != -1) {
 +                              *r_ele = (BMElem *)BM_edge_at_index_find(bm, edge_index);
 +                      }
 +                      else if (face_index != -1) {
 +                              *r_ele = (BMElem *)BM_face_at_index_find(bm, face_index);
 +                      }
 +              }
 +      }
 +      else {
 +              Base *base = view_layer->basact;
 +              Object *obedit = base->object;
 +              BMEditMesh *em = BKE_editmesh_from_object(obedit);
 +              BMesh *bm = em->bm;
 +              *r_base = base;
 +              *r_ele = BM_mesh_active_elem_get(bm);
 +      }
 +      return (*r_ele != NULL);
 +}
 +
 +static bool edbm_preselect_or_active_init_viewcontext(
 +        bContext *C,
 +        ViewContext *vc,
 +        Base **r_base,
 +        BMElem **r_ele)
 +{
 +      em_setup_viewcontext(C, vc);
 +      bool ok = edbm_preselect_or_active(C, r_base, r_ele);
 +      if (ok) {
 +              ED_view3d_viewcontext_init_object(vc, (*r_base)->object);
 +      }
 +      return ok;
 +}
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Face at Cursor
 + * \{ */
 +
 +static int edbm_polybuild_face_at_cursor_invoke(
 +        bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
 +{
 +      float center[3];
 +      bool changed = false;
 +
 +      ViewContext vc;
 +      Base *basact = NULL;
 +      BMElem *ele_act = NULL;
 +      edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act);
 +      BMEditMesh *em = vc.em;
 +      BMesh *bm = em->bm;
 +
 +      invert_m4_m4(vc.obedit->imat, vc.obedit->obmat);
 +      ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
 +
 +      edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX);
 +
 +      if (ele_act == NULL || ele_act->head.htype == BM_FACE) {
 +              /* Just add vert */
 +              copy_v3_v3(center, ED_view3d_cursor3d_get(vc.scene, vc.v3d)->location);
 +              mul_v3_m4v3(center, vc.obedit->obmat, center);
 +              ED_view3d_win_to_3d_int(vc.v3d, vc.ar, center, event->mval, center);
 +              mul_m4_v3(vc.obedit->imat, center);
 +
 +              BMVert *v_new = BM_vert_create(bm, center, NULL, BM_CREATE_NOP);
 +              edbm_flag_disable_all_multi(vc.view_layer, BM_ELEM_SELECT);
 +              BM_vert_select_set(bm, v_new, true);
 +              BM_select_history_store(bm, v_new);
 +              changed = true;
 +      }
 +      else if (ele_act->head.htype == BM_EDGE) {
 +              BMEdge *e_act = (BMEdge *)ele_act;
 +              BMFace *f_reference = e_act->l ? e_act->l->f : NULL;
 +
 +              mid_v3_v3v3(center, e_act->v1->co, e_act->v2->co);
 +              mul_m4_v3(vc.obedit->obmat, center);
 +              ED_view3d_win_to_3d_int(vc.v3d, vc.ar, center, event->mval, center);
 +              mul_m4_v3(vc.obedit->imat, center);
 +
 +              BMVert *v_tri[3];
 +              v_tri[0] = e_act->v1;
 +              v_tri[1] = e_act->v2;
 +              v_tri[2] = BM_vert_create(bm, center, NULL, BM_CREATE_NOP);
 +              if (e_act->l && e_act->l->v == v_tri[0]) {
 +                      SWAP(BMVert *, v_tri[0], v_tri[1]);
 +              }
 +              // BMFace *f_new =
 +              BM_face_create_verts(bm, v_tri, 3, f_reference, BM_CREATE_NOP, true);
 +
 +              edbm_flag_disable_all_multi(vc.view_layer, BM_ELEM_SELECT);
 +              BM_vert_select_set(bm, v_tri[2], true);
 +              BM_select_history_store(bm, v_tri[2]);
 +              changed = true;
 +      }
 +      else if (ele_act->head.htype == BM_VERT) {
 +              BMVert *v_act = (BMVert *)ele_act;
 +              BMEdge *e_pair[2] = {NULL};
 +
 +              if (v_act->e != NULL) {
 +                      for (uint allow_wire = 0; allow_wire < 2 && (e_pair[1] == NULL); allow_wire++) {
 +                              int i = 0;
 +                              BMEdge *e_iter = v_act->e;
 +                              do {
 +                                      if ((BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN) == false) &&
 +                                          (allow_wire ? BM_edge_is_wire(e_iter) : BM_edge_is_boundary(e_iter)))
 +                                      {
 +                                              if (i == 2) {
 +                                                      e_pair[0] = e_pair[1] = NULL;
 +                                                      break;
 +                                              }
 +                                              e_pair[i++] = e_iter;
 +                                      }
 +                              } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_act)) != v_act->e);
 +                      }
 +              }
 +
 +              if (e_pair[1] != NULL) {
 +                      /* Quad from edge pair. */
 +                      if (BM_edge_calc_length_squared(e_pair[0]) <
 +                          BM_edge_calc_length_squared(e_pair[1]))
 +                      {
 +                              SWAP(BMEdge *, e_pair[0], e_pair[1]);
 +                      }
 +
 +                      BMFace *f_reference = e_pair[0]->l ? e_pair[0]->l->f : NULL;
 +
 +                      mul_v3_m4v3(center, vc.obedit->obmat, v_act->co);
 +                      ED_view3d_win_to_3d_int(vc.v3d, vc.ar, center, event->mval, center);
 +                      mul_m4_v3(vc.obedit->imat, center);
 +
 +                      BMVert *v_quad[4];
 +                      v_quad[0] = v_act;
 +                      v_quad[1] = BM_edge_other_vert(e_pair[0], v_act);
 +                      v_quad[2] = BM_vert_create(bm, center, NULL, BM_CREATE_NOP);
 +                      v_quad[3] = BM_edge_other_vert(e_pair[1], v_act);
 +                      if (e_pair[0]->l && e_pair[0]->l->v == v_quad[0]) {
 +                              SWAP(BMVert *, v_quad[1], v_quad[3]);
 +                      }
 +                      // BMFace *f_new =
 +                      BM_face_create_verts(bm, v_quad, 4, f_reference, BM_CREATE_NOP, true);
 +
 +                      edbm_flag_disable_all_multi(vc.view_layer, BM_ELEM_SELECT);
 +                      BM_vert_select_set(bm, v_quad[2], true);
 +                      BM_select_history_store(bm, v_quad[2]);
 +                      changed = true;
 +              }
 +              else {
 +                      /* Just add edge */
 +                      mul_m4_v3(vc.obedit->obmat, center);
 +                      ED_view3d_win_to_3d_int(vc.v3d, vc.ar, v_act->co, event->mval, center);
 +                      mul_m4_v3(vc.obedit->imat, center);
 +
 +                      BMVert *v_new = BM_vert_create(bm, center, NULL, BM_CREATE_NOP);
 +
 +                      BM_edge_create(bm, v_act, v_new, NULL, BM_CREATE_NOP);
 +
 +                      BM_vert_select_set(bm, v_new, true);
 +                      BM_select_history_store(bm, v_new);
 +                      changed = true;
 +              }
 +      }
 +
 +      if (changed) {
 +              EDBM_mesh_normals_update(em);
 +              EDBM_update_generic(em, true, true);
 +
 +              if (basact != NULL) {
 +                      if (vc.view_layer->basact != basact) {
 +                              ED_object_base_activate(C, basact);
 +                      }
 +              }
 +
 +              WM_event_add_mousemove(C);
 +
 +              return OPERATOR_FINISHED;
 +      }
 +      else {
 +              return OPERATOR_CANCELLED;
 +      }
 +}
 +
 +void MESH_OT_polybuild_face_at_cursor(wmOperatorType *ot)
 +{
 +      /* identifiers */
 +      ot->name = "Poly Build Face at Cursor";
 +      ot->idname = "MESH_OT_polybuild_face_at_cursor";
-       ot->description = "";
 +
 +      /* api callbacks */
 +      ot->invoke = edbm_polybuild_face_at_cursor_invoke;
 +      ot->poll = EDBM_view3d_poll;
 +
 +      /* flags */
 +      ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 +
 +      /* to give to transform */
 +      Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY);
 +}
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Split at Cursor
 + * \{ */
 +
 +static int edbm_polybuild_split_at_cursor_invoke(
 +        bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
 +{
 +      float center[3];
 +      bool changed = false;
 +
 +      ViewContext vc;
 +      Base *basact = NULL;
 +      BMElem *ele_act = NULL;
 +      edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act);
 +      BMEditMesh *em = vc.em;
 +      BMesh *bm = em->bm;
 +
 +      invert_m4_m4(vc.obedit->imat, vc.obedit->obmat);
 +      ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
 +
 +      edbm_selectmode_ensure(vc.scene, vc.em, SCE_SELECT_VERTEX);
 +
 +      if (ele_act == NULL || ele_act->head.hflag == BM_FACE) {
 +              return OPERATOR_PASS_THROUGH;
 +      }
 +      else if (ele_act->head.htype == BM_EDGE) {
 +              BMEdge *e_act = (BMEdge *)ele_act;
 +              mid_v3_v3v3(center, e_act->v1->co, e_act->v2->co);
 +              mul_m4_v3(vc.obedit->obmat, center);
 +              ED_view3d_win_to_3d_int(vc.v3d, vc.ar, center, event->mval, center);
 +              mul_m4_v3(vc.obedit->imat, center);
 +
 +              const float fac = line_point_factor_v3(center, e_act->v1->co, e_act->v2->co);
 +              BMVert *v_new = BM_edge_split(bm, e_act, e_act->v1, NULL, CLAMPIS(fac, 0.0f, 1.0f));
 +              copy_v3_v3(v_new->co, center);
 +
 +              edbm_flag_disable_all_multi(vc.view_layer, BM_ELEM_SELECT);
 +              BM_vert_select_set(bm, v_new, true);
 +              BM_select_history_store(bm, v_new);
 +              changed = true;
 +      }
 +      else if (ele_act->head.htype == BM_VERT) {
 +              /* Just do nothing, allow dragging. */
 +              return OPERATOR_FINISHED;
 +      }
 +
 +      if (changed) {
 +              EDBM_mesh_normals_update(em);
 +              EDBM_update_generic(em, true, true);
 +
 +              WM_event_add_mousemove(C);
 +
 +              if (vc.view_layer->basact != basact) {
 +                      ED_object_base_activate(C, basact);
 +              }
 +
 +              return OPERATOR_FINISHED;
 +      }
 +      else {
 +              return OPERATOR_CANCELLED;
 +      }
 +}
 +
 +void MESH_OT_polybuild_split_at_cursor(wmOperatorType *ot)
 +{
 +      /* identifiers */
 +      ot->name = "Poly Build Split at Cursor";
 +      ot->idname = "MESH_OT_polybuild_split_at_cursor";
-       ot->description = "";
 +
 +      /* api callbacks */
 +      ot->invoke = edbm_polybuild_split_at_cursor_invoke;
 +      ot->poll = EDBM_view3d_poll;
 +
 +      /* flags */
 +      ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 +
 +      /* to give to transform */
 +      Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR_DUMMY);
 +}
 +
 +/** \} */
 +
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Dissolve at Cursor
 + *
 + * \{ */
 +
 +static int edbm_polybuild_dissolve_at_cursor_invoke(
 +        bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
 +{
 +      bool changed = false;
 +
 +      ViewContext vc;
 +      Base *basact = NULL;
 +      BMElem *ele_act = NULL;
 +      edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act);
 +      BMEditMesh *em = vc.em;
 +      BMesh *bm = em->bm;
 +
 +      if (ele_act->head.htype == BM_EDGE) {
 +              BMEdge *e_act = (BMEdge *)ele_act;
 +              BMLoop *l_a, *l_b;
 +              if (BM_edge_loop_pair(e_act, &l_a, &l_b)) {
 +                      BMFace *f_new = BM_faces_join_pair(bm, l_a, l_b, true);
 +                      if (f_new) {
 +                              changed = true;
 +                      }
 +              }
 +      }
 +      else if (ele_act->head.htype == BM_VERT) {
 +              BMVert *v_act = (BMVert *)ele_act;
 +              if (BM_vert_is_edge_pair(v_act)) {
 +                      BM_edge_collapse(
 +                              bm, v_act->e, v_act,
 +                              true, true);
 +              }
 +              else {
 +                      /* too involved to do inline */
 +
 +                      /* Avoid using selection so failure wont leave modified state. */
 +                      EDBM_flag_disable_all(em, BM_ELEM_TAG);
 +                      BM_elem_flag_enable(v_act, BM_ELEM_TAG);
 +
 +                      if (!EDBM_op_callf(em, op,
 +                                         "dissolve_verts verts=%hv use_face_split=%b use_boundary_tear=%b",
 +                                         BM_ELEM_TAG, false, false))
 +                      {
 +                              return OPERATOR_CANCELLED;
 +                      }
 +              }
 +              changed = true;
 +      }
 +
 +      if (changed) {
 +              edbm_flag_disable_all_multi(vc.view_layer, BM_ELEM_SELECT);
 +
 +              EDBM_mesh_normals_update(em);
 +              EDBM_update_generic(em, true, true);
 +
 +              if (vc.view_layer->basact != basact) {
 +                      ED_object_base_activate(C, basact);
 +              }
 +
 +              WM_event_add_mousemove(C);
 +
 +              return OPERATOR_FINISHED;
 +      }
 +      else {
 +              return OPERATOR_CANCELLED;
 +      }
 +}
 +
 +void MESH_OT_polybuild_dissolve_at_cursor(wmOperatorType *ot)
 +{
 +      /* identifiers */
 +      ot->name = "Poly Build Dissolve at Cursor";
 +      ot->idname = "MESH_OT_polybuild_dissolve_at_cursor";
 +
 +      /* api callbacks */
 +      ot->invoke = edbm_polybuild_dissolve_at_cursor_invoke;
 +      ot->poll = EDBM_view3d_poll;
 +
 +      /* flags */
 +      ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 +}
 +
 +/** \} */
index be8210a41983b96355af6db5f576d015211a2834,05e9a34f492121d33794383f38708769637d6b43..0c4e370ef8c33e20b2a078f1176af3c4f46e63c8
@@@ -1230,33 -1204,3 +1230,32 @@@ void VIEW3D_OT_properties(wmOperatorTyp
        /* flags */
        ot->flag = 0;
  }
-       ot->description = "";
 +
 +static int view3d_object_mode_menu(bContext *C, wmOperator *op)
 +{
 +      Object *ob = CTX_data_active_object(C);
 +      if (ob == NULL) {
 +              BKE_report(op->reports, RPT_WARNING, "No active object found");
 +              return OPERATOR_CANCELLED;
 +      }
 +      else if (((ob->mode & OB_MODE_EDIT) == 0) && (ELEM(ob->type, OB_ARMATURE))) {
 +              ED_object_mode_toggle(C, OB_MODE_POSE);
 +              return OPERATOR_CANCELLED;
 +      }
 +      else {
 +              UI_pie_menu_invoke(C, "VIEW3D_MT_object_mode_pie", CTX_wm_window(C)->eventstate);
 +              return OPERATOR_CANCELLED;
 +      }
 +}
 +
 +void VIEW3D_OT_object_mode_pie_or_toggle(wmOperatorType *ot)
 +{
 +      ot->name = "Object Mode Menu";
 +      ot->idname = "VIEW3D_OT_object_mode_pie_or_toggle";
 +
 +      ot->exec = view3d_object_mode_menu;
 +      ot->poll = ED_operator_view3d_active;
 +
 +      /* flags */
 +      ot->flag = 0;
 +}
index 34709e434ac81737055dfee88e645b0dd604593a,0000000000000000000000000000000000000000..96fbe1d48a68403428cc7cbd0a623902843e4852
mode 100644,000000..100644
--- /dev/null
@@@ -1,1091 -1,0 +1,1090 @@@
-       ot->description = "";
 +/*
 + * ***** 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.
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/editors/space_view3d/view3d_gizmo_ruler.c
 + *  \ingroup spview3d
 + */
 +
 +#include "BLI_listbase.h"
 +#include "BLI_string.h"
 +#include "BLI_rect.h"
 +#include "BLI_math.h"
 +#include "BLI_utildefines.h"
 +
 +#include "BLT_translation.h"
 +
 +#include "BKE_context.h"
 +#include "BKE_gpencil.h"
 +#include "BKE_main.h"
 +
 +#include "BKE_object.h"
 +#include "BKE_unit.h"
 +#include "BKE_material.h"
 +
 +#include "DNA_meshdata_types.h"
 +#include "DNA_object_types.h"
 +#include "DNA_gpencil_types.h"
 +#include "DNA_view3d_types.h"
 +
 +#include "BIF_gl.h"
 +
 +#include "ED_gizmo_utils.h"
 +#include "ED_gpencil.h"
 +#include "ED_screen.h"
 +#include "ED_transform_snap_object_context.h"
 +#include "ED_view3d.h"
 +
 +#include "UI_resources.h"
 +#include "UI_interface.h"
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "RNA_access.h"
 +
 +#include "WM_api.h"
 +#include "WM_types.h"
 +#include "WM_toolsystem.h"
 +
 +#include "view3d_intern.h"  /* own include */
 +
 +#include "GPU_immediate.h"
 +#include "GPU_immediate_util.h"
 +#include "GPU_select.h"
 +#include "GPU_state.h"
 +
 +#include "BLF_api.h"
 +
 +
 +static const char *view3d_gzgt_ruler_id = "VIEW3D_GGT_ruler";
 +
 +
 +#define MVAL_MAX_PX_DIST 12.0f
 +
 +/* -------------------------------------------------------------------- */
 +/* 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
 +};
 +
 +/* keep smaller then selection, since we may want click elsewhere without selecting a ruler */
 +#define RULER_PICK_DIST 12.0f
 +#define RULER_PICK_DIST_SQ (RULER_PICK_DIST * RULER_PICK_DIST)
 +
 +/* not clicking on a point */
 +#define PART_LINE 0xff
 +
 +/* -------------------------------------------------------------------- */
 +/* Ruler Info (wmGizmoGroup customdata) */
 +
 +enum {
 +      RULER_STATE_NORMAL = 0,
 +      RULER_STATE_DRAG
 +};
 +
 +enum {
 +      RULER_SNAP_OK = (1 << 0),
 +};
 +
 +typedef struct RulerInfo {
 +      // ListBase items;
 +      int      item_active;
 +      int flag;
 +      int snap_flag;
 +      int state;
 +
 +      struct SnapObjectContext *snap_context;
 +
 +      /* wm state */
 +      wmWindow *win;
 +      ScrArea *sa;
 +      ARegion *ar;  /* re-assigned every modal update */
 +} RulerInfo;
 +
 +/* -------------------------------------------------------------------- */
 +/* Ruler Item (two or three points) */
 +
 +typedef struct RulerItem {
 +      wmGizmo gz;
 +
 +      /* worldspace coords, middle being optional */
 +      float co[3][3];
 +
 +      int   flag;
 +      int   raycast_dir;  /* RULER_DIRECTION_* */
 +} RulerItem;
 +
 +typedef struct RulerInteraction {
 +      /* selected coord */
 +      char  co_index; /* 0 -> 2 */
 +      float drag_start_co[3];
 +      uint inside_region : 1;
 +} RulerInteraction;
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Internal Ruler Utilities
 + * \{ */
 +
 +static RulerItem *ruler_item_add(wmGizmoGroup *gzgroup)
 +{
 +      /* could pass this as an arg */
 +      const wmGizmoType *gzt_ruler = WM_gizmotype_find("VIEW3D_GT_ruler_item", true);
 +      RulerItem *ruler_item = (RulerItem *)WM_gizmo_new_ptr(gzt_ruler, gzgroup, NULL);
 +      WM_gizmo_set_flag(&ruler_item->gz, WM_GIZMO_DRAW_MODAL, true);
 +      return ruler_item;
 +}
 +
 +static void ruler_item_remove(bContext *C, wmGizmoGroup *gzgroup, RulerItem *ruler_item)
 +{
 +      WM_gizmo_unlink(&gzgroup->gizmos, gzgroup->parent_gzmap, &ruler_item->gz, C);
 +}
 +
 +static void ruler_item_as_string(RulerItem *ruler_item, UnitSettings *unit,
 +                                 char *numstr, size_t numstr_size, int prec)
 +{
 +      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]);
 +
 +              if (unit->system == USER_UNIT_NONE) {
 +                      BLI_snprintf(numstr, numstr_size, "%.*f°", prec, RAD2DEGF(ruler_angle));
 +              }
 +              else {
 +                      bUnit_AsString2(
 +                              numstr, numstr_size, (double)ruler_angle,
 +                              prec, B_UNIT_ROTATION, unit, false);
 +              }
 +      }
 +      else {
 +              const float ruler_len = len_v3v3(ruler_item->co[0],
 +                                               ruler_item->co[2]);
 +
 +              if (unit->system == USER_UNIT_NONE) {
 +                      BLI_snprintf(numstr, numstr_size, "%.*f", prec, ruler_len);
 +              }
 +              else {
 +                      bUnit_AsString2(
 +                              numstr, numstr_size, (double)(ruler_len * unit->scale_length),
 +                              prec, B_UNIT_LENGTH, unit, false);
 +              }
 +      }
 +}
 +
 +static bool view3d_ruler_pick(
 +        wmGizmoGroup *gzgroup, RulerItem *ruler_item, const float mval[2],
 +        int *r_co_index)
 +{
 +      RulerInfo *ruler_info = gzgroup->customdata;
 +      ARegion *ar = ruler_info->ar;
 +      bool found = false;
 +
 +      float dist_best = RULER_PICK_DIST_SQ;
 +      int co_index_best = -1;
 +
 +      {
 +              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;
 +                              found = true;
 +
 +                              {
 +                                      const 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;
 +                              found = true;
 +
 +                              {
 +                                      const 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;
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +
 +      *r_co_index = co_index_best;
 +      return found;
 +}
 +
 +/**
 + * Ensure the 'snap_context' is only cached while dragging,
 + * needed since the user may toggle modes between tool use.
 + */
 +static void ruler_state_set(bContext *C, RulerInfo *ruler_info, int state)
 +{
 +      Main *bmain = CTX_data_main(C);
 +      if (state == ruler_info->state) {
 +              return;
 +      }
 +
 +      /* always remove */
 +      if (ruler_info->snap_context) {
 +              ED_transform_snap_object_context_destroy(ruler_info->snap_context);
 +              ruler_info->snap_context = NULL;
 +      }
 +
 +      if (state == RULER_STATE_NORMAL) {
 +              /* pass */
 +      }
 +      else if (state == RULER_STATE_DRAG) {
 +              ruler_info->snap_context = ED_transform_snap_object_context_create_view3d(
 +                      bmain, CTX_data_scene(C), CTX_data_depsgraph(C), 0,
 +                      ruler_info->ar, CTX_wm_view3d(C));
 +      }
 +      else {
 +              BLI_assert(0);
 +      }
 +
 +      ruler_info->state = state;
 +}
 +
 +static void view3d_ruler_item_project(
 +        RulerInfo *ruler_info, float r_co[3],
 +        const int xy[2])
 +{
 +      ED_view3d_win_to_3d_int(ruler_info->sa->spacedata.first, ruler_info->ar, r_co, xy, r_co);
 +}
 +
 +/* use for mousemove events */
 +static bool view3d_ruler_item_mousemove(
 +        RulerInfo *ruler_info, RulerItem *ruler_item, const int mval[2],
 +        const bool do_thickness, const bool do_snap)
 +{
 +      RulerInteraction *inter = ruler_item->gz.interaction_data;
 +      const float eps_bias = 0.0002f;
 +      float dist_px = MVAL_MAX_PX_DIST * U.pixelsize;  /* snap dist */
 +
 +      ruler_info->snap_flag &= ~RULER_SNAP_OK;
 +
 +      if (ruler_item) {
 +              float *co = ruler_item->co[inter->co_index];
 +              /* restore the initial depth */
 +              copy_v3_v3(co, inter->drag_start_co);
 +              view3d_ruler_item_project(ruler_info, co, mval);
 +              if (do_thickness && inter->co_index != 1) {
 +                      // Scene *scene = CTX_data_scene(C);
 +                      // View3D *v3d = ruler_info->sa->spacedata.first;
 +                      const float mval_fl[2] = {UNPACK2(mval)};
 +                      float ray_normal[3];
 +                      float ray_start[3];
 +                      float *co_other;
 +
 +                      co_other = ruler_item->co[inter->co_index == 0 ? 2 : 0];
 +
 +                      if (ED_transform_snap_object_project_view3d(
 +                              ruler_info->snap_context,
 +                              SCE_SNAP_MODE_FACE,
 +                              &(const struct SnapObjectParams){
 +                                  .snap_select = SNAP_ALL,
 +                                  .use_object_edit_cage = true,
 +                              },
 +                              mval_fl, &dist_px,
 +                              co, ray_normal))
 +                      {
 +                              negate_v3(ray_normal);
 +                              /* add some bias */
 +                              madd_v3_v3v3fl(ray_start, co, ray_normal, eps_bias);
 +                              ED_transform_snap_object_project_ray(
 +                                      ruler_info->snap_context,
 +                                      &(const struct SnapObjectParams){
 +                                          .snap_select = SNAP_ALL,
 +                                          .use_object_edit_cage = true,
 +                                      },
 +                                      ray_start, ray_normal, NULL,
 +                                      co_other, NULL);
 +                      }
 +              }
 +              else if (do_snap) {
 +                      const float mval_fl[2] = {UNPACK2(mval)};
 +
 +                      if (ED_transform_snap_object_project_view3d(
 +                              ruler_info->snap_context,
 +                              (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE),
 +                              &(const struct SnapObjectParams){
 +                                  .snap_select = SNAP_ALL,
 +                                  .use_object_edit_cage = true,
 +                                  .use_occlusion_test = true,
 +                              },
 +                              mval_fl, &dist_px,
 +                              co, NULL))
 +                      {
 +                              ruler_info->snap_flag |= RULER_SNAP_OK;
 +                      }
 +              }
 +              return true;
 +      }
 +      else {
 +              return false;
 +      }
 +}
 +
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Ruler/Grease Pencil Conversion
 + * \{ */
 +
 +#define RULER_ID "RulerData3D"
 +static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup)
 +{
 +      // RulerInfo *ruler_info = gzgroup->customdata;
 +      Main *bmain = CTX_data_main(C);
 +      Scene *scene = CTX_data_scene(C);
 +
 +      bGPdata *gpd;
 +      bGPDlayer *gpl;
 +      bGPDframe *gpf;
 +      bGPDstroke *gps;
 +      RulerItem *ruler_item;
 +      const char *ruler_name = RULER_ID;
 +      bool changed = false;
 +
 +      if (scene->gpd == NULL) {
 +              scene->gpd = BKE_gpencil_data_addnew(bmain, "Annotations");
 +      }
 +      gpd = scene->gpd;
 +
 +      gpl = BLI_findstring(&gpd->layers, ruler_name, offsetof(bGPDlayer, info));
 +      if (gpl == NULL) {
 +              gpl = BKE_gpencil_layer_addnew(gpd, ruler_name, false);
 +              copy_v4_v4(gpl->color, U.gpencil_new_layer_col);
 +              gpl->thickness = 1;
 +              gpl->flag |= GP_LAYER_HIDE;
 +      }
 +
 +      gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_NEW);
 +      BKE_gpencil_free_strokes(gpf);
 +
 +      for (ruler_item = gzgroup->gizmos.first; ruler_item; ruler_item = (RulerItem *)ruler_item->gz.next) {
 +              bGPDspoint *pt;
 +              int j;
 +
 +              /* allocate memory for a new stroke */
 +              gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
 +              if (ruler_item->flag & RULERITEM_USE_ANGLE) {
 +                      gps->totpoints = 3;
 +                      pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
 +                      for (j = 0; j < 3; j++) {
 +                              copy_v3_v3(&pt->x, ruler_item->co[j]);
 +                              pt->pressure = 1.0f;
 +                              pt->strength = 1.0f;
 +                              pt++;
 +                      }
 +              }
 +              else {
 +                      gps->totpoints = 2;
 +                      pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
 +                      for (j = 0; j < 3; j += 2) {
 +                              copy_v3_v3(&pt->x, ruler_item->co[j]);
 +                              pt->pressure = 1.0f;
 +                              pt->strength = 1.0f;
 +                              pt++;
 +                      }
 +              }
 +              gps->flag = GP_STROKE_3DSPACE;
 +              gps->thickness = 3;
 +
 +              BLI_addtail(&gpf->strokes, gps);
 +              changed = true;
 +      }
 +
 +      return changed;
 +}
 +
 +static bool view3d_ruler_from_gpencil(const bContext *C, wmGizmoGroup *gzgroup)
 +{
 +      Scene *scene = CTX_data_scene(C);
 +      bool changed = false;
 +
 +      if (scene->gpd) {
 +              bGPDlayer *gpl;
 +              const char *ruler_name = RULER_ID;
 +              gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
 +              if (gpl) {
 +                      bGPDframe *gpf;
 +                      gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV);
 +                      if (gpf) {
 +                              bGPDstroke *gps;
 +                              for (gps = gpf->strokes.first; gps; gps = gps->next) {
 +                                      bGPDspoint *pt = gps->points;
 +                                      int j;
 +                                      RulerItem *ruler_item = NULL;
 +                                      if (gps->totpoints == 3) {
 +                                              ruler_item = ruler_item_add(gzgroup);
 +                                              for (j = 0; j < 3; j++) {
 +                                                      copy_v3_v3(ruler_item->co[j], &pt->x);
 +                                                      pt++;
 +                                              }
 +                                              ruler_item->flag |= RULERITEM_USE_ANGLE;
 +                                              changed = true;
 +                                      }
 +                                      else if (gps->totpoints == 2) {
 +                                              ruler_item = ruler_item_add(gzgroup);
 +                                              for (j = 0; j < 3; j += 2) {
 +                                                      copy_v3_v3(ruler_item->co[j], &pt->x);
 +                                                      pt++;
 +                                              }
 +                                              changed = true;
 +                                      }
 +                              }
 +                      }
 +              }
 +      }
 +
 +      return changed;
 +}
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Ruler Item Gizmo Type
 + * \{ */
 +
 +static void gizmo_ruler_draw(const bContext *C, wmGizmo *gz)
 +{
 +      Scene *scene = CTX_data_scene(C);
 +      UnitSettings *unit = &scene->unit;
 +      RulerInfo *ruler_info = gz->parent_gzgroup->customdata;
 +      RulerItem *ruler_item = (RulerItem *)gz;
 +      ARegion *ar = ruler_info->ar;
 +      RegionView3D *rv3d = ar->regiondata;
 +      const float cap_size = 4.0f;
 +      const float bg_margin = 4.0f * U.pixelsize;
 +      const float arc_size = 64.0f * U.pixelsize;
 +#define ARC_STEPS 24
 +      const int arc_steps = ARC_STEPS;
 +      const float color_act[4] = {1.0f, 1.0f, 1.0f, 1.0f};
 +      const float color_base[4] = {0.0f, 0.0f, 0.0f, 1.0f};
 +      unsigned char color_text[3];
 +      unsigned char color_wire[3];
 +      float color_back[4] = {1.0f, 1.0f, 1.0f, 0.5f};
 +
 +      /* anti-aliased lines for more consistent appearance */
 +      GPU_line_smooth(true);
 +      GPU_line_width(1.0f);
 +
 +      BLF_enable(blf_mono_font, BLF_ROTATION);
 +      BLF_size(blf_mono_font, 14 * U.pixelsize, U.dpi);
 +      BLF_rotation(blf_mono_font, 0.0f);
 +
 +      UI_GetThemeColor3ubv(TH_TEXT, color_text);
 +      UI_GetThemeColor3ubv(TH_WIRE, color_wire);
 +
 +      /* Avoid white on white text. (TODO Fix by using theme) */
 +      if ((int)color_text[0] + (int)color_text[1] + (int)color_text[2] > 127 * 3 * 0.6f) {
 +              copy_v3_fl(color_back, 0.0f);
 +      }
 +
 +      const bool is_act = (gz->flag & WM_GIZMO_DRAW_HOVER);
 +      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);
 +      }
 +
 +      GPU_blend(true);
 +
 +      const uint shdr_pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +
 +      if (ruler_item->flag & RULERITEM_USE_ANGLE) {
 +              immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
 +
 +              float viewport_size[4];
 +              GPU_viewport_size_get_f(viewport_size);
 +              immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
 +
 +              immUniform1i("colors_len", 2);  /* "advanced" mode */
 +              const float *col = is_act ? color_act : color_base;
 +              immUniformArray4fv("colors", (float *)(float[][4]){{0.67f, 0.67f, 0.67f, 1.0f}, {col[0], col[1], col[2], col[3]}}, 2);
 +              immUniform1f("dash_width", 6.0f);
 +
 +              immBegin(GPU_PRIM_LINE_STRIP, 3);
 +
 +              immVertex2fv(shdr_pos, co_ss[0]);
 +              immVertex2fv(shdr_pos, co_ss[1]);
 +              immVertex2fv(shdr_pos, co_ss[2]);
 +
 +              immEnd();
 +
 +              immUnbindProgram();
 +
 +              immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 +
 +              /* arc */
 +              {
 +                      float dir_tmp[3];
 +                      float co_tmp[3];
 +                      float arc_ss_coord[2];
 +
 +                      float dir_a[3];
 +                      float dir_b[3];
 +                      float quat[4];
 +                      float axis[3];
 +                      float angle;
 +                      const float px_scale = (ED_view3d_pixel_size_no_ui_scale(rv3d, ruler_item->co[1]) *
 +                                              min_fff(arc_size,
 +                                                      len_v2v2(co_ss[0], co_ss[1]) / 2.0f,
 +                                                      len_v2v2(co_ss[2], co_ss[1]) / 2.0f));
 +
 +                      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);
 +
 +                      immUniformColor3ubv(color_wire);
 +
 +                      immBegin(GPU_PRIM_LINE_STRIP, arc_steps + 1);
 +
 +                      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_coord, V3D_PROJ_TEST_NOP);
 +                              mul_qt_v3(quat, dir_tmp);
 +
 +                              immVertex2fv(shdr_pos, arc_ss_coord);
 +                      }
 +
 +                      immEnd();
 +              }
 +
 +              /* 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);
 +
 +                      GPU_blend(true);
 +
 +                      immUniformColor3ubv(color_wire);
 +
 +                      immBegin(GPU_PRIM_LINES, 8);
 +
 +                      madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +                      madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, -cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +
 +                      madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +                      madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, -cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +
 +                      /* angle vertex */
 +                      immVertex2f(shdr_pos, co_ss[1][0] - cap_size, co_ss[1][1] - cap_size);
 +                      immVertex2f(shdr_pos, co_ss[1][0] + cap_size, co_ss[1][1] + cap_size);
 +                      immVertex2f(shdr_pos, co_ss[1][0] - cap_size, co_ss[1][1] + cap_size);
 +                      immVertex2f(shdr_pos, co_ss[1][0] + cap_size, co_ss[1][1] - cap_size);
 +
 +                      immEnd();
 +
 +                      GPU_blend(false);
 +              }
 +
 +              /* text */
 +              char numstr[256];
 +              float numstr_size[2];
 +              float posit[2];
 +              const int prec = 2;  /* XXX, todo, make optional */
 +
 +              ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
 +
 +              BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
 +
 +              posit[0] = co_ss[1][0] + (cap_size * 2.0f);
 +              posit[1] = co_ss[1][1] - (numstr_size[1] / 2.0f);
 +
 +              /* draw text (bg) */
 +              {
 +                      immUniformColor4fv(color_back);
 +                      GPU_blend(true);
 +                      immRectf(shdr_pos,
 +                               posit[0] - bg_margin,                  posit[1] - bg_margin,
 +                               posit[0] + bg_margin + numstr_size[0], posit[1] + bg_margin + numstr_size[1]);
 +                      GPU_blend(false);
 +              }
 +
 +              immUnbindProgram();
 +
 +              /* draw text */
 +              {
 +                      BLF_color3ubv(blf_mono_font, color_text);
 +                      BLF_position(blf_mono_font, posit[0], posit[1], 0.0f);
 +                      BLF_rotation(blf_mono_font, 0.0f);
 +                      BLF_draw(blf_mono_font, numstr, sizeof(numstr));
 +              }
 +      }
 +      else {
 +              immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
 +
 +              float viewport_size[4];
 +              GPU_viewport_size_get_f(viewport_size);
 +              immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
 +
 +              immUniform1i("colors_len", 2);  /* "advanced" mode */
 +              const float *col = is_act ? color_act : color_base;
 +              immUniformArray4fv("colors", (float *)(float[][4]){{0.67f, 0.67f, 0.67f, 1.0f}, {col[0], col[1], col[2], col[3]}}, 2);
 +              immUniform1f("dash_width", 6.0f);
 +
 +              immBegin(GPU_PRIM_LINES, 2);
 +
 +              immVertex2fv(shdr_pos, co_ss[0]);
 +              immVertex2fv(shdr_pos, co_ss[2]);
 +
 +              immEnd();
 +
 +              immUnbindProgram();
 +
 +              immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 +
 +              sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]);
 +
 +              /* capping */
 +              {
 +                      float rot_90_vec[2] = {-dir_ruler[1], dir_ruler[0]};
 +                      float cap[2];
 +
 +                      normalize_v2(rot_90_vec);
 +
 +                      GPU_blend(true);
 +
 +                      immUniformColor3ubv(color_wire);
 +
 +                      immBegin(GPU_PRIM_LINES, 4);
 +
 +                      madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +                      madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, -cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +
 +                      madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +                      madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, -cap_size);
 +                      immVertex2fv(shdr_pos, cap);
 +
 +                      immEnd();
 +
 +                      GPU_blend(false);
 +              }
 +
 +              /* text */
 +              char numstr[256];
 +              float numstr_size[2];
 +              const int prec = 6;  /* XXX, todo, make optional */
 +              float posit[2];
 +
 +              ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
 +
 +              BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
 +
 +              mid_v2_v2v2(posit, co_ss[0], co_ss[2]);
 +
 +              /* center text */
 +              posit[0] -= numstr_size[0] / 2.0f;
 +              posit[1] -= numstr_size[1] / 2.0f;
 +
 +              /* draw text (bg) */
 +              {
 +                      immUniformColor4fv(color_back);
 +                      GPU_blend(true);
 +                      immRectf(shdr_pos,
 +                               posit[0] - bg_margin,                  posit[1] - bg_margin,
 +                               posit[0] + bg_margin + numstr_size[0], posit[1] + bg_margin + numstr_size[1]);
 +                      GPU_blend(false);
 +              }
 +
 +              immUnbindProgram();
 +
 +              /* draw text */
 +              {
 +                      BLF_color3ubv(blf_mono_font, color_text);
 +                      BLF_position(blf_mono_font, posit[0], posit[1], 0.0f);
 +                      BLF_draw(blf_mono_font, numstr, sizeof(numstr));
 +              }
 +      }
 +
 +      GPU_line_smooth(false);
 +
 +      BLF_disable(blf_mono_font, BLF_ROTATION);
 +
 +#undef ARC_STEPS
 +
 +      /* draw snap */
 +      if ((ruler_info->snap_flag & RULER_SNAP_OK) &&
 +          (ruler_info->state == RULER_STATE_DRAG) &&
 +          (ruler_item->gz.interaction_data != NULL))
 +      {
 +              RulerInteraction *inter = ruler_item->gz.interaction_data;
 +              /* size from drawSnapping */
 +              const float size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
 +              float co_ss_snap[3];
 +              ED_view3d_project_float_global(ar, ruler_item->co[inter->co_index], co_ss_snap, V3D_PROJ_TEST_NOP);
 +
 +              uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 +
 +              immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 +              immUniformColor4fv(color_act);
 +
 +              imm_draw_circle_wire_2d(pos, co_ss_snap[0], co_ss_snap[1], size * U.pixelsize, 32);
 +
 +              immUnbindProgram();
 +      }
 +}
 +
 +static int gizmo_ruler_test_select(
 +        bContext *UNUSED(C), wmGizmo *gz, const int mval[2])
 +{
 +      RulerItem *ruler_item_pick = (RulerItem *)gz;
 +      float mval_fl[2] = {UNPACK2(mval)};
 +      int co_index;
 +
 +      /* select and drag */
 +      if (view3d_ruler_pick(gz->parent_gzgroup, ruler_item_pick, mval_fl, &co_index)) {
 +              if (co_index == -1) {
 +                      if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
 +                              return PART_LINE;
 +                      }
 +              }
 +              else {
 +                      return co_index;
 +              }
 +      }
 +      return -1;
 +}
 +
 +static int gizmo_ruler_modal(
 +        bContext *C, wmGizmo *gz, const wmEvent *event,
 +        eWM_GizmoFlagTweak UNUSED(tweak_flag))
 +{
 +      bool do_draw = false;
 +      int exit_code = OPERATOR_RUNNING_MODAL;
 +      RulerInfo *ruler_info = gz->parent_gzgroup->customdata;
 +      RulerItem *ruler_item = (RulerItem *)gz;
 +      RulerInteraction *inter = ruler_item->gz.interaction_data;
 +      ARegion *ar = CTX_wm_region(C);
 +
 +      ruler_info->ar = ar;
 +
 +      switch (event->type) {
 +              case MOUSEMOVE:
 +              {
 +                      if (ruler_info->state == RULER_STATE_DRAG) {
 +                              if (view3d_ruler_item_mousemove(
 +                                      ruler_info, ruler_item, event->mval,
 +                                      event->shift != 0, event->ctrl != 0))
 +                              {
 +                                      do_draw = true;
 +                              }
 +                              inter->inside_region = BLI_rcti_isect_pt_v(&ar->winrct, &event->x);
 +                      }
 +                      break;
 +              }
 +      }
 +      if (do_draw) {
 +              ED_region_tag_redraw(ar);
 +      }
 +      return exit_code;
 +}
 +
 +static int gizmo_ruler_invoke(
 +        bContext *C, wmGizmo *gz, const wmEvent *event)
 +{
 +      wmGizmoGroup *gzgroup = gz->parent_gzgroup;
 +      RulerInfo *ruler_info = gzgroup->customdata;
 +      RulerItem *ruler_item_pick = (RulerItem *)gz;
 +      RulerInteraction *inter = MEM_callocN(sizeof(RulerInteraction), __func__);
 +      gz->interaction_data = inter;
 +
 +      ARegion *ar = ruler_info->ar;
 +
 +      const float mval_fl[2] = {UNPACK2(event->mval)};
 +
 +      /* select and drag */
 +      if (gz->highlight_part == PART_LINE) {
 +              if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
 +                      /* Add Center Point */
 +                      ruler_item_pick->flag |= RULERITEM_USE_ANGLE;
 +                      inter->co_index = 1;
 +                      ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
 +
 +                      /* find the factor */
 +                      {
 +                              float co_ss[2][2];
 +                              float fac;
 +
 +                              ED_view3d_project_float_global(ar, ruler_item_pick->co[0], co_ss[0], V3D_PROJ_TEST_NOP);
 +                              ED_view3d_project_float_global(ar, ruler_item_pick->co[2], co_ss[1], V3D_PROJ_TEST_NOP);
 +
 +                              fac = line_point_factor_v2(mval_fl, co_ss[0], co_ss[1]);
 +                              CLAMP(fac, 0.0f, 1.0f);
 +
 +                              interp_v3_v3v3(ruler_item_pick->co[1],
 +                                             ruler_item_pick->co[0],
 +                                             ruler_item_pick->co[2], fac);
 +                      }
 +
 +                      /* update the new location */
 +                      view3d_ruler_item_mousemove(
 +                              ruler_info, ruler_item_pick, event->mval,
 +                              event->shift != 0, event->ctrl != 0);
 +              }
 +      }
 +      else {
 +              inter->co_index = gz->highlight_part;
 +              ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
 +
 +              /* store the initial depth */
 +              copy_v3_v3(inter->drag_start_co, ruler_item_pick->co[inter->co_index]);
 +      }
 +
 +      return OPERATOR_RUNNING_MODAL;
 +}
 +
 +static void gizmo_ruler_exit(bContext *C, wmGizmo *gz, const bool cancel)
 +{
 +      wmGizmoGroup *gzgroup = gz->parent_gzgroup;
 +      RulerInfo *ruler_info = gzgroup->customdata;
 +
 +      if (!cancel) {
 +              if (ruler_info->state == RULER_STATE_DRAG) {
 +                      RulerItem *ruler_item = (RulerItem *)gz;
 +                      RulerInteraction *inter = gz->interaction_data;
 +                      /* rubber-band angle removal */
 +                      if (!inter->inside_region) {
 +                              if ((inter->co_index == 1) && (ruler_item->flag & RULERITEM_USE_ANGLE)) {
 +                                      ruler_item->flag &= ~RULERITEM_USE_ANGLE;
 +                              }
 +                              else {
 +                                      /* Not ideal, since the ruler isn't a mode and we don't want to override delete key
 +                                       * use dragging out of the view for removal. */
 +                                      ruler_item_remove(C, gzgroup, ruler_item);
 +                                      ruler_item = NULL;
 +                                      gz = NULL;
 +                                      inter = NULL;
 +                              }
 +                      }
 +                      if (ruler_info->snap_flag & RULER_SNAP_OK) {
 +                              ruler_info->snap_flag &= ~RULER_SNAP_OK;
 +                      }
 +                      ruler_state_set(C, ruler_info, RULER_STATE_NORMAL);
 +              }
 +              /* We could convert only the current gizmo, for now just re-generate. */
 +              view3d_ruler_to_gpencil(C, gzgroup);
 +      }
 +
 +      if (gz) {
 +              MEM_SAFE_FREE(gz->interaction_data);
 +      }
 +
 +      ruler_state_set(C, ruler_info, RULER_STATE_NORMAL);
 +}
 +
 +static int gizmo_ruler_cursor_get(wmGizmo *gz)
 +{
 +      if (gz->highlight_part == PART_LINE) {
 +              return BC_CROSSCURSOR;
 +      }
 +      return BC_NSEW_SCROLLCURSOR;
 +}
 +
 +void VIEW3D_GT_ruler_item(wmGizmoType *gzt)
 +{
 +      /* identifiers */
 +      gzt->idname = "VIEW3D_GT_ruler_item";
 +
 +      /* api callbacks */
 +      gzt->draw = gizmo_ruler_draw;
 +      gzt->test_select = gizmo_ruler_test_select;
 +      gzt->modal = gizmo_ruler_modal;
 +      gzt->invoke = gizmo_ruler_invoke;
 +      gzt->exit = gizmo_ruler_exit;
 +      gzt->cursor_get = gizmo_ruler_cursor_get;
 +
 +      gzt->struct_size = sizeof(RulerItem);
 +}
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Ruler Gizmo Group
 + * \{ */
 +
 +static void WIDGETGROUP_ruler_setup(const bContext *C, wmGizmoGroup *gzgroup)
 +{
 +      RulerInfo *ruler_info = MEM_callocN(sizeof(RulerInfo), __func__);
 +
 +      if (view3d_ruler_from_gpencil(C, gzgroup)) {
 +              /* nop */
 +      }
 +
 +      wmWindow *win = CTX_wm_window(C);
 +      ScrArea *sa = CTX_wm_area(C);
 +      ARegion *ar = CTX_wm_region(C);
 +      ruler_info->win = win;
 +      ruler_info->sa = sa;
 +      ruler_info->ar = ar;
 +
 +      gzgroup->customdata = ruler_info;
 +}
 +
 +void VIEW3D_GGT_ruler(wmGizmoGroupType *gzgt)
 +{
 +      gzgt->name = "Ruler Widgets";
 +      gzgt->idname = view3d_gzgt_ruler_id;
 +
 +      gzgt->flag |= WM_GIZMOGROUPTYPE_SCALE | WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL;
 +
 +      gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
 +      gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
 +
 +      gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool;
 +      gzgt->setup = WIDGETGROUP_ruler_setup;
 +}
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Add Ruler Operator
 + * \{ */
 +
 +static bool view3d_ruler_poll(bContext *C)
 +{
 +      bToolRef_Runtime *tref_rt = WM_toolsystem_runtime_from_context((bContext *)C);
 +      if ((tref_rt == NULL) ||
 +          !STREQ(view3d_gzgt_ruler_id, tref_rt->gizmo_group) ||
 +          CTX_wm_region_view3d(C) == NULL)
 +      {
 +              return false;
 +      }
 +      return true;
 +}
 +
 +static int view3d_ruler_add_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
 +{
 +      ARegion *ar = CTX_wm_region(C);
 +      View3D *v3d = CTX_wm_view3d(C);
 +      RegionView3D *rv3d = ar->regiondata;
 +
 +      wmGizmoMap *gzmap = ar->gizmo_map;
 +      wmGizmoGroup *gzgroup = WM_gizmomap_group_find(gzmap, view3d_gzgt_ruler_id);
 +      const bool use_depth = (v3d->shading.type >= OB_SOLID);
 +
 +      /* Create new line */
 +      RulerItem *ruler_item;
 +      ruler_item = ruler_item_add(gzgroup);
 +
 +      /* This is a little weak, but there is no real good way to tweak directly. */
 +      WM_gizmo_highlight_set(gzmap, &ruler_item->gz);
 +      if (WM_operator_name_call(
 +              C, "GIZMOGROUP_OT_gizmo_tweak",
 +              WM_OP_INVOKE_REGION_WIN, NULL) == OPERATOR_RUNNING_MODAL)
 +      {
 +              RulerInfo *ruler_info = gzgroup->customdata;
 +              RulerInteraction *inter = ruler_item->gz.interaction_data;
 +              if (use_depth) {
 +                      /* snap the first point added, not essential but handy */
 +                      inter->co_index = 0;
 +                      view3d_ruler_item_mousemove(ruler_info, ruler_item, event->mval, false, true);
 +                      copy_v3_v3(inter->drag_start_co, ruler_item->co[inter->co_index]);
 +              }
 +              else {
 +                      negate_v3_v3(inter->drag_start_co, rv3d->ofs);
 +                      copy_v3_v3(ruler_item->co[0], inter->drag_start_co);
 +                      view3d_ruler_item_project(ruler_info, ruler_item->co[0], event->mval);
 +              }
 +
 +              copy_v3_v3(ruler_item->co[2], ruler_item->co[0]);
 +              ruler_item->gz.highlight_part = inter->co_index = 2;
 +      }
 +      return OPERATOR_FINISHED;
 +}
 +
 +void VIEW3D_OT_ruler_add(wmOperatorType *ot)
 +{
 +      /* identifiers */
 +      ot->name = "Ruler Add";
 +      ot->idname = "VIEW3D_OT_ruler_add";
 +
 +      ot->invoke = view3d_ruler_add_invoke;
 +      ot->poll = view3d_ruler_poll;
 +
 +      /* flags */
 +      ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
 +}
 +
 +/** \} */
index 33613d97f03c0ade3ff9dad49cb0bcf2beaf7be9,e9287d83a9aee1a201d4282e3a5f70fe3fbb21ed..54a09f7ce9a59dcee3611a1635db7d1df8ab92cc
@@@ -120,10 -116,8 +120,11 @@@ static void wm_operatortype_append__end
        if (ot->name == NULL) {
                CLOG_ERROR(WM_LOG_OPERATORS, "Operator '%s' has no name property", ot->idname);
        }
+       BLI_assert((ot->description == NULL) || (ot->description[0]));
  
 +      /* Allow calling _begin without _end in operatortype creation. */
 +      WM_operatortype_props_advanced_end(ot);
 +
        /* XXX All ops should have a description but for now allow them not to. */
        RNA_def_struct_ui_text(ot->srna, ot->name, ot->description ? ot->description : UNDOCUMENTED_OPERATOR_TIP);
        RNA_def_struct_identifier(&BLENDER_RNA, ot->srna, ot->idname);