1071 lines
29 KiB
C
1071 lines
29 KiB
C
/*
|
|
* ***** 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 "DNA_object_types.h"
|
|
#include "DNA_gpencil_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_math.h"
|
|
#include "BLI_blenlib.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_unit.h"
|
|
#include "BKE_gpencil.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 */
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Snapping (could be own function) */
|
|
/* NOTE - this is not very nice use of transform snapping */
|
|
#include "ED_transform.h"
|
|
|
|
#define MVAL_MAX_PX_DIST 12.0f
|
|
|
|
/**
|
|
* Convenience function for performing snapping.
|
|
*
|
|
* \param C Context.
|
|
* \param r_co hit location.
|
|
* \param r_no hit normal (optional).
|
|
* \param co_ss Screenspace coordinate.
|
|
* \param use_depth Snap to the closest element, use when using more than one snap type.
|
|
* \param use_obedit Use editmode cage.
|
|
* \param use_vert Snap to verts.
|
|
* \param use_edge Snap to edges.
|
|
* \param use_face Snap to faces.
|
|
* \return Snap success
|
|
*/
|
|
static bool ED_view3d_snap_co(bContext *C, float r_co[3], float r_no[3], const float co_ss[2],
|
|
bool use_depth, bool use_obedit,
|
|
bool use_vert, bool use_edge, bool use_face)
|
|
{
|
|
float dist_px = MVAL_MAX_PX_DIST; /* snap dist */
|
|
float r_no_dummy[3];
|
|
float ray_dist = TRANSFORM_DIST_MAX_RAY;
|
|
bool ret = false;
|
|
float *r_no_ptr = r_no ? r_no : r_no_dummy;
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
ARegion *ar = CTX_wm_region(C);
|
|
struct Object *obedit = use_obedit ? CTX_data_edit_object(C) : NULL;
|
|
|
|
BLI_assert(use_vert || use_edge || use_face);
|
|
|
|
/* try snap edge, then face if it fails */
|
|
if (use_vert) {
|
|
ret |= snapObjectsEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_VERTEX,
|
|
co_ss, &dist_px, r_co, r_no_ptr, &ray_dist, SNAP_ALL);
|
|
}
|
|
if (use_edge && (ret == false || use_depth)) {
|
|
if (use_depth == false) ray_dist = TRANSFORM_DIST_MAX_RAY;
|
|
ret |= snapObjectsEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_EDGE,
|
|
co_ss, &dist_px, r_co, r_no_ptr, &ray_dist, SNAP_ALL);
|
|
}
|
|
if (use_face && (ret == false || use_depth)) {
|
|
if (use_depth == false) ray_dist = TRANSFORM_DIST_MAX_RAY;
|
|
ret |= snapObjectsEx(scene, NULL, v3d, ar, obedit, SCE_SNAP_MODE_FACE,
|
|
co_ss, &dist_px, r_co, r_no_ptr, &ray_dist, SNAP_ALL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool ED_view3d_snap_ray(bContext *C, float r_co[3],
|
|
const float ray_start[3], const float ray_normal[3])
|
|
{
|
|
float dist_px = MVAL_MAX_PX_DIST; /* snap dist */
|
|
float r_no_dummy[3];
|
|
float ray_dist = TRANSFORM_DIST_MAX_RAY;
|
|
bool ret;
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
|
struct Object *obedit = CTX_data_edit_object(C);
|
|
|
|
/* try snap edge, then face if it fails */
|
|
ret = snapObjectsRayEx(scene, NULL, NULL, NULL, obedit, SCE_SNAP_MODE_FACE,
|
|
NULL, NULL,
|
|
ray_start, ray_normal, &ray_dist,
|
|
NULL, &dist_px, r_co, r_no_dummy, SNAP_ALL);
|
|
|
|
return ret;
|
|
}
|
|
/* done snapping */
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* 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)
|
|
|
|
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;
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Ruler Info (one per session) */
|
|
|
|
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;
|
|
float drag_start_co[3];
|
|
|
|
/* wm state */
|
|
wmWindow *win;
|
|
ScrArea *sa;
|
|
void *draw_handle_pixel;
|
|
ARegion *ar; /* re-assigned every modal update */
|
|
} 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;
|
|
}
|
|
|
|
static void ruler_item_remove(RulerInfo *ruler_info, RulerItem *ruler_item)
|
|
{
|
|
BLI_remlink(&ruler_info->items, ruler_item);
|
|
MEM_freeN(ruler_item);
|
|
}
|
|
|
|
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 void ruler_item_as_string(RulerItem *ruler_item, UnitSettings *unit,
|
|
char *numstr, size_t numstr_size, int prec)
|
|
{
|
|
const bool do_split = (unit->flag & USER_UNIT_OPT_SPLIT) != 0;
|
|
|
|
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_AsString(numstr, numstr_size,
|
|
(double)ruler_angle,
|
|
prec, unit->system, B_UNIT_ROTATION, do_split, 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_AsString(numstr, numstr_size,
|
|
(double)(ruler_len * unit->scale_length),
|
|
prec, unit->system, B_UNIT_LENGTH, do_split, false);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
{
|
|
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;
|
|
ruler_item_best = ruler_item;
|
|
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#define RULER_ID "RulerData3D"
|
|
static bool view3d_ruler_to_gpencil(bContext *C, RulerInfo *ruler_info)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
bGPDlayer *gpl;
|
|
bGPDframe *gpf;
|
|
bGPDstroke *gps;
|
|
RulerItem *ruler_item;
|
|
const char *ruler_name = RULER_ID;
|
|
bool changed = false;
|
|
|
|
if (scene->gpd == NULL) {
|
|
scene->gpd = gpencil_data_addnew("GPencil");
|
|
}
|
|
|
|
gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
|
|
if (gpl == NULL) {
|
|
gpl = gpencil_layer_addnew(scene->gpd, ruler_name, false);
|
|
gpl->thickness = 1;
|
|
gpl->flag |= GP_LAYER_HIDE;
|
|
}
|
|
|
|
gpf = gpencil_layer_getframe(gpl, CFRA, true);
|
|
free_gpencil_strokes(gpf);
|
|
|
|
for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->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++;
|
|
}
|
|
}
|
|
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++;
|
|
}
|
|
}
|
|
gps->flag = GP_STROKE_3DSPACE;
|
|
BLI_addtail(&gpf->strokes, gps);
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool view3d_ruler_from_gpencil(bContext *C, RulerInfo *ruler_info)
|
|
{
|
|
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 = gpencil_layer_getframe(gpl, CFRA, false);
|
|
if (gpf) {
|
|
bGPDstroke *gps;
|
|
for (gps = gpf->strokes.first; gps; gps = gps->next) {
|
|
bGPDspoint *pt = gps->points;
|
|
int j;
|
|
if (gps->totpoints == 3) {
|
|
RulerItem *ruler_item = ruler_item_add(ruler_info);
|
|
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) {
|
|
RulerItem *ruler_item = ruler_item_add(ruler_info);
|
|
for (j = 0; j < 3; j += 2) {
|
|
copy_v3_v3(ruler_item->co[j], &pt->x);
|
|
pt++;
|
|
}
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* 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;
|
|
RulerItem *ruler_item;
|
|
RulerInfo *ruler_info = arg;
|
|
RegionView3D *rv3d = 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);
|
|
BLF_rotation(blf_mono_font, 0.0f);
|
|
|
|
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) {
|
|
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;
|
|
const float px_scale = (ED_view3d_pixel_size(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);
|
|
|
|
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 */
|
|
|
|
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]);
|
|
|
|
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);
|
|
UI_draw_roundbox_corner_set(UI_CNR_ALL);
|
|
UI_draw_roundbox(
|
|
pos[0] - bg_margin, pos[1] - bg_margin,
|
|
pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
|
|
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 {
|
|
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 */
|
|
float pos[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(pos, co_ss[0], co_ss[2]);
|
|
|
|
/* center text */
|
|
pos[0] -= numstr_size[0] / 2.0f;
|
|
pos[1] -= numstr_size[1] / 2.0f;
|
|
|
|
/* draw text (bg) */
|
|
glColor4ubv(color_back);
|
|
UI_draw_roundbox_corner_set(UI_CNR_ALL);
|
|
UI_draw_roundbox(pos[0] - bg_margin, pos[1] - bg_margin,
|
|
pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
|
|
bg_radius);
|
|
/* draw text */
|
|
glColor3ubv(color_text);
|
|
BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
|
|
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
|
|
|
|
/* draw snap */
|
|
if ((ruler_info->snap_flag & RULER_SNAP_OK) && (ruler_info->state == RULER_STATE_DRAG)) {
|
|
ruler_item = ruler_item_active_get(ruler_info);
|
|
if (ruler_item) {
|
|
/* size from drawSnapping */
|
|
const float size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
|
|
float co_ss[3];
|
|
ED_view3d_project_float_global(ar, ruler_item->co[ruler_item->co_index], co_ss, V3D_PROJ_TEST_NOP);
|
|
|
|
cpack(color_act);
|
|
circ(co_ss[0], co_ss[1], size * U.pixelsize);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* 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(RulerInfo *ruler_info, float r_co[3],
|
|
const int xy[2])
|
|
{
|
|
ED_view3d_win_to_3d_int(ruler_info->ar, r_co, xy, r_co);
|
|
}
|
|
|
|
/* use for mousemove events */
|
|
static bool view3d_ruler_item_mousemove(bContext *C, RulerInfo *ruler_info, const int mval[2],
|
|
const bool do_thickness, const bool do_snap)
|
|
{
|
|
const float eps_bias = 0.0002f;
|
|
RulerItem *ruler_item = ruler_item_active_get(ruler_info);
|
|
|
|
ruler_info->snap_flag &= ~RULER_SNAP_OK;
|
|
|
|
if (ruler_item) {
|
|
float *co = ruler_item->co[ruler_item->co_index];
|
|
/* restore the initial depth */
|
|
copy_v3_v3(co, ruler_info->drag_start_co);
|
|
view3d_ruler_item_project(ruler_info, co, mval);
|
|
if (do_thickness && ruler_item->co_index != 1) {
|
|
const float mval_fl[2] = {UNPACK2(mval)};
|
|
float ray_normal[3];
|
|
float ray_start[3];
|
|
float *co_other;
|
|
|
|
co_other = ruler_item->co[ruler_item->co_index == 0 ? 2 : 0];
|
|
|
|
if (ED_view3d_snap_co(C, co, ray_normal, mval_fl, true, false,
|
|
false, false, true))
|
|
{
|
|
negate_v3(ray_normal);
|
|
/* add some bias */
|
|
madd_v3_v3v3fl(ray_start, co, ray_normal, eps_bias);
|
|
ED_view3d_snap_ray(C, co_other,
|
|
ray_start, ray_normal);
|
|
}
|
|
}
|
|
else if (do_snap) {
|
|
const float mval_fl[2] = {UNPACK2(mval)};
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
bool use_depth = (v3d->drawtype >= OB_SOLID);
|
|
bool is_hit = ED_view3d_snap_co(C, co, NULL, mval_fl, use_depth, false,
|
|
true, true, use_depth);
|
|
|
|
if (is_hit) {
|
|
ruler_info->snap_flag |= RULER_SNAP_OK;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void view3d_ruler_header_update(ScrArea *sa)
|
|
{
|
|
const char *text = "Ctrl+LMB: Add, "
|
|
"Del: Remove, "
|
|
"Ctrl+Drag: Snap, "
|
|
"Shift+Drag: Thickness, "
|
|
"Ctrl+C: Copy Value, "
|
|
"Enter: Store, "
|
|
"Esc: Cancel";
|
|
|
|
ED_area_headerprint(sa, text);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Operator callbacks */
|
|
|
|
static int view3d_ruler_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
ScrArea *sa = CTX_wm_area(C);
|
|
ARegion *ar = CTX_wm_region(C);
|
|
RulerInfo *ruler_info;
|
|
|
|
ruler_info = MEM_callocN(sizeof(RulerInfo), "RulerInfo");
|
|
|
|
if (view3d_ruler_from_gpencil(C, ruler_info)) {
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
|
|
}
|
|
|
|
op->customdata = ruler_info;
|
|
|
|
ruler_info->win = win;
|
|
ruler_info->sa = sa;
|
|
ruler_info->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ruler_info_draw_pixel,
|
|
ruler_info, REGION_DRAW_POST_PIXEL);
|
|
|
|
view3d_ruler_header_update(sa);
|
|
|
|
op->flag |= OP_IS_MODAL_CURSOR_REGION;
|
|
|
|
WM_cursor_modal_set(win, BC_CROSSCURSOR);
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static void 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;
|
|
}
|
|
|
|
static int view3d_ruler_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
bool do_draw = false;
|
|
int exit_code = OPERATOR_RUNNING_MODAL;
|
|
RulerInfo *ruler_info = op->customdata;
|
|
ScrArea *sa = CTX_wm_area(C);
|
|
ARegion *ar = CTX_wm_region(C);
|
|
RegionView3D *rv3d = ar->regiondata;
|
|
|
|
/* its possible to change spaces while running the operator [#34894] */
|
|
if (UNLIKELY(sa != ruler_info->sa)) {
|
|
exit_code = OPERATOR_FINISHED;
|
|
goto exit;
|
|
}
|
|
|
|
ruler_info->ar = ar;
|
|
|
|
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(&ar->winrct, &event->x)) {
|
|
ruler_item->flag &= ~RULERITEM_USE_ANGLE;
|
|
do_draw = true;
|
|
}
|
|
}
|
|
if (ruler_info->snap_flag & RULER_SNAP_OK) {
|
|
ruler_info->snap_flag &= ~RULER_SNAP_OK;
|
|
do_draw = true;
|
|
}
|
|
ruler_info->state = RULER_STATE_NORMAL;
|
|
}
|
|
}
|
|
else {
|
|
if (ruler_info->state == RULER_STATE_NORMAL) {
|
|
|
|
if (event->ctrl ||
|
|
/* weak - but user friendly */
|
|
BLI_listbase_is_empty(&ruler_info->items))
|
|
{
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
const bool use_depth = (v3d->drawtype >= OB_SOLID);
|
|
|
|
/* Create new line */
|
|
RulerItem *ruler_item_prev = ruler_item_active_get(ruler_info);
|
|
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);
|
|
|
|
if (use_depth) {
|
|
/* snap the first point added, not essential but handy */
|
|
ruler_item->co_index = 0;
|
|
view3d_ruler_item_mousemove(C, ruler_info, event->mval, false, true);
|
|
copy_v3_v3(ruler_info->drag_start_co, ruler_item->co[ruler_item->co_index]);
|
|
}
|
|
else {
|
|
/* initial depth either previous ruler, view offset */
|
|
if (ruler_item_prev) {
|
|
copy_v3_v3(ruler_info->drag_start_co, ruler_item_prev->co[ruler_item_prev->co_index]);
|
|
}
|
|
else {
|
|
negate_v3_v3(ruler_info->drag_start_co, rv3d->ofs);
|
|
}
|
|
|
|
copy_v3_v3(ruler_item->co[0], ruler_info->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->co_index = 2;
|
|
|
|
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;
|
|
|
|
/* 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(C, ruler_info, event->mval,
|
|
event->shift != 0, event->ctrl != 0);
|
|
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;
|
|
|
|
/* store the initial depth */
|
|
copy_v3_v3(ruler_info->drag_start_co, ruler_item_pick->co[ruler_item_pick->co_index]);
|
|
|
|
do_draw = true;
|
|
}
|
|
}
|
|
else {
|
|
exit_code = OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CKEY:
|
|
{
|
|
if (event->ctrl) {
|
|
RulerItem *ruler_item = ruler_item_active_get(ruler_info);
|
|
if (ruler_item) {
|
|
const int prec = 8;
|
|
char numstr[256];
|
|
Scene *scene = CTX_data_scene(C);
|
|
UnitSettings *unit = &scene->unit;
|
|
|
|
ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
|
|
WM_clipboard_text_set((void *) numstr, false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case RIGHTCTRLKEY:
|
|
case LEFTCTRLKEY:
|
|
{
|
|
WM_event_add_mousemove(C);
|
|
break;
|
|
}
|
|
case MOUSEMOVE:
|
|
{
|
|
if (ruler_info->state == RULER_STATE_DRAG) {
|
|
if (view3d_ruler_item_mousemove(C, ruler_info, event->mval,
|
|
event->shift != 0, event->ctrl != 0))
|
|
{
|
|
do_draw = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ESCKEY:
|
|
{
|
|
do_draw = true;
|
|
exit_code = OPERATOR_CANCELLED;
|
|
break;
|
|
}
|
|
case RETKEY:
|
|
{
|
|
view3d_ruler_to_gpencil(C, ruler_info);
|
|
do_draw = true;
|
|
exit_code = OPERATOR_FINISHED;
|
|
break;
|
|
}
|
|
case DELKEY:
|
|
{
|
|
if (event->val == KM_PRESS) {
|
|
if (ruler_info->state == RULER_STATE_NORMAL) {
|
|
RulerItem *ruler_item = ruler_item_active_get(ruler_info);
|
|
if (ruler_item) {
|
|
RulerItem *ruler_item_other = ruler_item->prev ? ruler_item->prev : ruler_item->next;
|
|
ruler_item_remove(ruler_info, ruler_item);
|
|
ruler_item_active_set(ruler_info, ruler_item_other);
|
|
do_draw = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
exit_code = OPERATOR_PASS_THROUGH;
|
|
break;
|
|
|
|
}
|
|
|
|
if (ruler_info->state == RULER_STATE_DRAG) {
|
|
op->flag &= ~OP_IS_MODAL_CURSOR_REGION;
|
|
}
|
|
else {
|
|
op->flag |= OP_IS_MODAL_CURSOR_REGION;
|
|
}
|
|
|
|
if (do_draw) {
|
|
view3d_ruler_header_update(sa);
|
|
|
|
/* all 3d views draw rulers */
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
|
|
}
|
|
|
|
exit:
|
|
if (ELEM(exit_code, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
|
|
WM_cursor_modal_restore(ruler_info->win);
|
|
|
|
view3d_ruler_end(C, ruler_info);
|
|
view3d_ruler_free(ruler_info);
|
|
op->customdata = NULL;
|
|
|
|
ED_area_headerprint(sa, NULL);
|
|
}
|
|
|
|
return exit_code;
|
|
}
|
|
|
|
void VIEW3D_OT_ruler(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Ruler/Protractor";
|
|
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 = 0;
|
|
}
|