2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2008 Blender Foundation.
19 * All rights reserved.
22 * Contributor(s): Blender Foundation
24 * ***** END GPL LICENSE BLOCK *****
27 /** \file blender/editors/space_view3d/view3d_edit.c
30 * 3D view manipulation/operators.
38 #include "DNA_armature_types.h"
39 #include "DNA_camera_types.h"
40 #include "DNA_curve_types.h"
41 #include "DNA_object_types.h"
42 #include "DNA_scene_types.h"
43 #include "DNA_gpencil_types.h"
45 #include "MEM_guardedalloc.h"
47 #include "BLI_blenlib.h"
49 #include "BLI_utildefines.h"
51 #include "BKE_action.h"
52 #include "BKE_armature.h"
53 #include "BKE_camera.h"
54 #include "BKE_context.h"
56 #include "BKE_gpencil.h"
57 #include "BKE_layer.h"
58 #include "BKE_library.h"
60 #include "BKE_object.h"
61 #include "BKE_paint.h"
62 #include "BKE_report.h"
63 #include "BKE_scene.h"
64 #include "BKE_screen.h"
66 #include "DEG_depsgraph.h"
67 #include "DEG_depsgraph_query.h"
71 #include "WM_message.h"
73 #include "RNA_access.h"
74 #include "RNA_define.h"
76 #include "ED_armature.h"
77 #include "ED_particle.h"
78 #include "ED_screen.h"
79 #include "ED_transform.h"
81 #include "ED_view3d.h"
82 #include "ED_transform_snap_object_context.h"
84 #include "UI_resources.h"
88 #include "view3d_intern.h" /* own include */
90 /* -------------------------------------------------------------------- */
91 /** \name Generic View Operator Properties
94 enum eV3D_OpPropFlag {
95 V3D_OP_PROP_MOUSE_CO = (1 << 0),
96 V3D_OP_PROP_DELTA = (1 << 1),
97 V3D_OP_PROP_USE_ALL_REGIONS = (1 << 2),
98 V3D_OP_PROP_USE_MOUSE_INIT = (1 << 3),
101 static void view3d_operator_properties_common(wmOperatorType *ot, const enum eV3D_OpPropFlag flag)
103 if (flag & V3D_OP_PROP_MOUSE_CO) {
105 prop = RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Region Position X", "", 0, INT_MAX);
106 RNA_def_property_flag(prop, PROP_HIDDEN);
107 prop = RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Region Position Y", "", 0, INT_MAX);
108 RNA_def_property_flag(prop, PROP_HIDDEN);
110 if (flag & V3D_OP_PROP_DELTA) {
111 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
113 if (flag & V3D_OP_PROP_USE_ALL_REGIONS) {
115 prop = RNA_def_boolean(ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
116 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
118 if (flag & V3D_OP_PROP_USE_MOUSE_INIT) {
119 /* Disable when view operators are initialized from buttons. */
121 prop = RNA_def_boolean(ot->srna, "use_mouse_init", true, "Mouse Init", "Use initial mouse position");
122 RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
128 /* -------------------------------------------------------------------- */
129 /** \name Generic View Operator Custom-Data
132 typedef struct ViewOpsData {
133 /** Context pointers (assigned by #viewops_data_alloc). */
140 Depsgraph *depsgraph;
142 /** Needed for continuous zoom. */
145 /** Viewport state on initialization, don't change afterwards. */
150 /** #wmEvent.x, y. */
152 /** Offset to use when #VIEWOPS_FLAG_USE_MOUSE_INIT is not set.
153 * so we can simulate pressing in the middle of the screen. */
154 int event_xy_offset[2];
155 /** #wmEvent.type that triggered the operator. */
158 /** Initial distance to 'ofs'. */
161 /** Trackball rotation only. */
167 /** Previous state (previous modal event handled). */
170 /** For operators that use time-steps (continuous zoom). */
174 /** Current state. */
176 /** Working copy of #RegionView3D.viewquat, needed for rotation calculation
177 * so we can apply snap to the view-port while keeping the unsnapped rotation
178 * here to use when snap is disabled and for continued calculation. */
183 bool axis_snap; /* view rotate only */
185 /** Use for orbit selection and auto-dist. */
190 #define TRACKBALLSIZE (1.1f)
192 static void calctrackballvec(const rcti *rect, const int event_xy[2], float vec[3])
194 const float radius = TRACKBALLSIZE;
195 const float t = radius / (float)M_SQRT2;
198 /* normalize x and y */
199 x = BLI_rcti_cent_x(rect) - event_xy[0];
200 x /= (float)(BLI_rcti_size_x(rect) / 4);
201 y = BLI_rcti_cent_y(rect) - event_xy[1];
202 y /= (float)(BLI_rcti_size_y(rect) / 2);
203 d = sqrtf(x * x + y * y);
204 if (d < t) { /* Inside sphere */
205 z = sqrtf(radius * radius - d * d);
207 else { /* On hyperbola */
213 vec[2] = -z; /* yah yah! */
217 * Allocate and fill in context pointers for #ViewOpsData
219 static void viewops_data_alloc(bContext *C, wmOperator *op)
221 ViewOpsData *vod = MEM_callocN(sizeof(ViewOpsData), "viewops data");
224 op->customdata = vod;
225 vod->bmain = CTX_data_main(C);
226 vod->depsgraph = CTX_data_depsgraph(C);
227 vod->scene = CTX_data_scene(C);
228 vod->sa = CTX_wm_area(C);
229 vod->ar = CTX_wm_region(C);
230 vod->v3d = vod->sa->spacedata.first;
231 vod->rv3d = vod->ar->regiondata;
234 void view3d_orbit_apply_dyn_ofs(
235 float r_ofs[3], const float ofs_init[3], const float viewquat_old[4],
236 const float viewquat_new[4], const float dyn_ofs[3])
239 invert_qt_qt_normalized(q, viewquat_old);
240 mul_qt_qtqt(q, q, viewquat_new);
242 invert_qt_normalized(q);
244 sub_v3_v3v3(r_ofs, ofs_init, dyn_ofs);
246 add_v3_v3(r_ofs, dyn_ofs);
249 static bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
251 static float lastofs[3] = {0, 0, 0};
254 const Depsgraph *depsgraph = CTX_data_depsgraph(C);
255 Scene *scene = CTX_data_scene(C);
256 ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
257 Object *ob_act_eval = OBACT(view_layer_eval);
258 Object *ob_act = DEG_get_original_object(ob_act_eval);
260 if (ob_act && (ob_act->mode & OB_MODE_ALL_PAINT) &&
261 /* with weight-paint + pose-mode, fall through to using calculateTransformCenter */
262 ((ob_act->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(ob_act)) == 0)
264 /* in case of sculpting use last average stroke position as a rotation
265 * center, in other cases it's not clear what rotation center shall be
266 * so just rotate around object origin
268 if (ob_act->mode & (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT)) {
270 BKE_paint_stroke_get_average(scene, ob_act_eval, stroke);
271 copy_v3_v3(lastofs, stroke);
274 copy_v3_v3(lastofs, ob_act_eval->obmat[3]);
278 else if (ob_act && (ob_act->mode & OB_MODE_EDIT) && (ob_act->type == OB_FONT)) {
279 Curve *cu = ob_act_eval->data;
280 EditFont *ef = cu->editfont;
284 for (i = 0; i < 4; i++) {
285 add_v2_v2(lastofs, ef->textcurs[i]);
287 mul_v2_fl(lastofs, 1.0f / 4.0f);
289 mul_m4_v3(ob_act_eval->obmat, lastofs);
293 else if (ob_act == NULL || ob_act->mode == OB_MODE_OBJECT) {
294 /* object mode use boundbox centers */
296 unsigned int tot = 0;
297 float select_center[3];
299 zero_v3(select_center);
300 for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
301 if (TESTBASE(base_eval)) {
302 /* use the boundbox if we can */
303 Object *ob_eval = base_eval->object;
305 if (ob_eval->bb && !(ob_eval->bb->flag & BOUNDBOX_DIRTY)) {
308 BKE_boundbox_calc_center_aabb(ob_eval->bb, cent);
310 mul_m4_v3(ob_eval->obmat, cent);
311 add_v3_v3(select_center, cent);
314 add_v3_v3(select_center, ob_eval->obmat[3]);
320 mul_v3_fl(select_center, 1.0f / (float)tot);
321 copy_v3_v3(lastofs, select_center);
326 /* If there's no selection, lastofs is unmodified and last value since static */
327 is_set = calculateTransformCenter(C, V3D_AROUND_CENTER_MEAN, lastofs, NULL);
330 copy_v3_v3(r_dyn_ofs, lastofs);
336 /** When enabled, rotate around the selection. */
337 VIEWOPS_FLAG_ORBIT_SELECT = (1 << 0),
338 /** When enabled, use the depth under the cursor for navigation. */
339 VIEWOPS_FLAG_DEPTH_NAVIGATE = (1 << 1),
341 * When enabled run #ED_view3d_persp_ensure this may switch out of
342 * camera view when orbiting or switch from ortho to perspective when auto-persp is enabled.
343 * Some operations don't require this (view zoom/pan or ndof where subtle rotation is common
344 * so we don't want it to trigger auto-perspective). */
345 VIEWOPS_FLAG_PERSP_ENSURE = (1 << 2),
346 /** When set, ignore any options that depend on initial cursor location. */
347 VIEWOPS_FLAG_USE_MOUSE_INIT = (1 << 3),
350 static enum eViewOpsFlag viewops_flag_from_args(bool use_select, bool use_depth)
352 enum eViewOpsFlag flag = 0;
354 flag |= VIEWOPS_FLAG_ORBIT_SELECT;
357 flag |= VIEWOPS_FLAG_DEPTH_NAVIGATE;
363 static enum eViewOpsFlag viewops_flag_from_prefs(void)
365 return viewops_flag_from_args(
366 (U.uiflag & USER_ORBIT_SELECTION) != 0,
367 (U.uiflag & USER_DEPTH_NAVIGATE) != 0);
371 * Calculate the values for #ViewOpsData
373 static void viewops_data_create(
374 bContext *C, wmOperator *op, const wmEvent *event,
375 enum eViewOpsFlag viewops_flag)
377 Depsgraph *depsgraph = CTX_data_depsgraph(C);
378 ViewOpsData *vod = op->customdata;
379 RegionView3D *rv3d = vod->rv3d;
381 /* Could do this more nicely. */
382 if ((viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) == 0) {
383 viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
386 /* we need the depth info before changing any viewport options */
387 if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
388 float fallback_depth_pt[3];
390 view3d_operator_needs_opengl(C); /* needed for zbuf drawing */
392 negate_v3_v3(fallback_depth_pt, rv3d->ofs);
394 vod->use_dyn_ofs = ED_view3d_autodist(
395 depsgraph, vod->ar, vod->v3d,
396 event->mval, vod->dyn_ofs, true, fallback_depth_pt);
399 vod->use_dyn_ofs = false;
402 if (viewops_flag & VIEWOPS_FLAG_PERSP_ENSURE) {
403 if (ED_view3d_persp_ensure(depsgraph, vod->v3d, vod->ar)) {
404 /* If we're switching from camera view to the perspective one,
405 * need to tag viewport update, so camera vuew and borders
406 * are properly updated.
408 ED_region_tag_redraw(vod->ar);
412 /* set the view from the camera, if view locking is enabled.
413 * we may want to make this optional but for now its needed always */
414 ED_view3d_camera_lock_init(depsgraph, vod->v3d, vod->rv3d);
416 vod->init.dist = rv3d->dist;
417 vod->init.camzoom = rv3d->camzoom;
418 copy_qt_qt(vod->init.quat, rv3d->viewquat);
419 vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
420 vod->init.event_xy[1] = vod->prev.event_xy[1] = event->y;
422 if (viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) {
423 vod->init.event_xy_offset[0] = 0;
424 vod->init.event_xy_offset[1] = 0;
427 /* Simulate the event starting in the middle of the region. */
428 vod->init.event_xy_offset[0] = BLI_rcti_cent_x(&vod->ar->winrct) - event->x;
429 vod->init.event_xy_offset[1] = BLI_rcti_cent_y(&vod->ar->winrct) - event->y;
432 vod->init.event_type = event->type;
433 copy_v3_v3(vod->init.ofs, rv3d->ofs);
435 copy_qt_qt(vod->curr.viewquat, rv3d->viewquat);
437 if (viewops_flag & VIEWOPS_FLAG_ORBIT_SELECT) {
439 if (view3d_orbit_calc_center(C, ofs) || (vod->use_dyn_ofs == false)) {
440 vod->use_dyn_ofs = true;
441 negate_v3_v3(vod->dyn_ofs, ofs);
442 viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
446 if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
447 if (vod->use_dyn_ofs) {
448 if (rv3d->is_persp) {
449 float my_origin[3]; /* original G.vd->ofs */
450 float my_pivot[3]; /* view */
453 /* locals for dist correction */
457 negate_v3_v3(my_origin, rv3d->ofs); /* ofs is flipped */
459 /* Set the dist value to be the distance from this 3d point
460 * this means youll always be able to zoom into it and panning wont go bad when dist was zero */
462 /* remove dist value */
463 upvec[0] = upvec[1] = 0;
464 upvec[2] = rv3d->dist;
465 copy_m3_m4(mat, rv3d->viewinv);
467 mul_m3_v3(mat, upvec);
468 sub_v3_v3v3(my_pivot, rv3d->ofs, upvec);
469 negate_v3(my_pivot); /* ofs is flipped */
471 /* find a new ofs value that is along the view axis (rather than the mouse location) */
472 closest_to_line_v3(dvec, vod->dyn_ofs, my_pivot, my_origin);
473 vod->init.dist = rv3d->dist = len_v3v3(my_pivot, dvec);
475 negate_v3_v3(rv3d->ofs, dvec);
478 const float mval_ar_mid[2] = {
479 (float)vod->ar->winx / 2.0f,
480 (float)vod->ar->winy / 2.0f};
482 ED_view3d_win_to_3d(vod->v3d, vod->ar, vod->dyn_ofs, mval_ar_mid, rv3d->ofs);
483 negate_v3(rv3d->ofs);
485 negate_v3(vod->dyn_ofs);
486 copy_v3_v3(vod->init.ofs, rv3d->ofs);
491 ED_view3d_win_to_vector(vod->ar, (const float[2]){UNPACK2(event->mval)}, vod->init.mousevec);
494 const int event_xy_offset[2] = {
495 event->x + vod->init.event_xy_offset[0],
496 event->y + vod->init.event_xy_offset[1],
498 /* For rotation with trackball rotation. */
499 calctrackballvec(&vod->ar->winrct, event_xy_offset, vod->init.trackvec);
504 negate_v3_v3(tvec, rv3d->ofs);
505 vod->init.zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
509 if (rv3d->persmat[2][1] < 0.0f)
510 vod->reverse = -1.0f;
512 rv3d->rflag |= RV3D_NAVIGATING;
515 static void viewops_data_free(bContext *C, wmOperator *op)
519 Paint *p = BKE_paint_get_active_from_context(C);
521 if (op->customdata) {
522 ViewOpsData *vod = op->customdata;
524 vod->rv3d->rflag &= ~RV3D_NAVIGATING;
527 WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
530 op->customdata = NULL;
533 ar = CTX_wm_region(C);
537 if (p && (p->flags & PAINT_FAST_NAVIGATE))
539 ED_region_tag_redraw(ar);
544 /* -------------------------------------------------------------------- */
545 /** \name View Rotate Operator
554 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
556 VIEW_MODAL_CONFIRM = 1, /* used for all view operations */
557 VIEWROT_MODAL_AXIS_SNAP_ENABLE = 2,
558 VIEWROT_MODAL_AXIS_SNAP_DISABLE = 3,
559 VIEWROT_MODAL_SWITCH_ZOOM = 4,
560 VIEWROT_MODAL_SWITCH_MOVE = 5,
561 VIEWROT_MODAL_SWITCH_ROTATE = 6,
564 /* called in transform_ops.c, on each regeneration of keymaps */
565 void viewrotate_modal_keymap(wmKeyConfig *keyconf)
567 static const EnumPropertyItem modal_items[] = {
568 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
570 {VIEWROT_MODAL_AXIS_SNAP_ENABLE, "AXIS_SNAP_ENABLE", 0, "Axis Snap", ""},
571 {VIEWROT_MODAL_AXIS_SNAP_DISABLE, "AXIS_SNAP_DISABLE", 0, "Axis Snap (Off)", ""},
573 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
574 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
576 {0, NULL, 0, NULL, NULL}
579 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Rotate Modal");
581 /* this function is called for each spacetype, only needs to add map once */
582 if (keymap && keymap->modal_items) return;
584 keymap = WM_modalkeymap_add(keyconf, "View3D Rotate Modal", modal_items);
586 /* disabled mode switching for now, can re-implement better, later on */
588 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
589 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
590 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
593 /* assign map to operators */
594 WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
597 static void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
599 if (vod->use_dyn_ofs) {
600 RegionView3D *rv3d = vod->rv3d;
601 view3d_orbit_apply_dyn_ofs(rv3d->ofs, vod->init.ofs, vod->init.quat, viewquat_new, vod->dyn_ofs);
605 static void viewrotate_apply_snap(ViewOpsData *vod)
607 const float axis_limit = DEG2RADF(45 / 3);
609 RegionView3D *rv3d = vod->rv3d;
611 float viewquat_inv[4];
612 float zaxis[3] = {0, 0, 1};
617 invert_qt_qt_normalized(viewquat_inv, vod->curr.viewquat);
619 mul_qt_v3(viewquat_inv, zaxis);
623 for (x = -1; x < 2; x++) {
624 for (y = -1; y < 2; y++) {
625 for (z = -1; z < 2; z++) {
627 float zaxis_test[3] = {x, y, z};
629 normalize_v3(zaxis_test);
631 if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
632 copy_v3_v3(zaxis_best, zaxis_test);
642 /* find the best roll */
643 float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
644 float viewquat_align[4]; /* viewquat aligned to zaxis_best */
645 float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
646 float best_angle = axis_limit;
649 /* viewquat_align is the original viewquat aligned to the snapped axis
650 * for testing roll */
651 rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
652 normalize_qt(viewquat_align);
653 mul_qt_qtqt(viewquat_align, vod->curr.viewquat, viewquat_align);
654 normalize_qt(viewquat_align);
655 invert_qt_qt_normalized(viewquat_align_inv, viewquat_align);
657 vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
658 normalize_qt(quat_snap);
659 invert_qt_normalized(quat_snap);
661 /* check if we can find the roll */
665 for (j = 0; j < 8; j++) {
667 float xaxis1[3] = {1, 0, 0};
668 float xaxis2[3] = {1, 0, 0};
669 float quat_final_inv[4];
671 axis_angle_to_quat(quat_roll, zaxis_best, (float)j * DEG2RADF(45.0f));
672 normalize_qt(quat_roll);
674 mul_qt_qtqt(quat_final, quat_snap, quat_roll);
675 normalize_qt(quat_final);
677 /* compare 2 vector angles to find the least roll */
678 invert_qt_qt_normalized(quat_final_inv, quat_final);
679 mul_qt_v3(viewquat_align_inv, xaxis1);
680 mul_qt_v3(quat_final_inv, xaxis2);
681 angle = angle_v3v3(xaxis1, xaxis2);
683 if (angle <= best_angle) {
686 copy_qt_qt(quat_best, quat_final);
691 /* lock 'quat_best' to an axis view if we can */
692 rv3d->view = ED_view3d_quat_to_axis_view(quat_best, 0.01f);
693 if (rv3d->view != RV3D_VIEW_USER) {
694 ED_view3d_quat_from_axis_view(rv3d->view, quat_best);
698 copy_qt_qt(quat_best, viewquat_align);
701 copy_qt_qt(rv3d->viewquat, quat_best);
703 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
707 static void viewrotate_apply(ViewOpsData *vod, const int event_xy[2])
709 RegionView3D *rv3d = vod->rv3d;
711 rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
713 if (U.flag & USER_TRACKBALL) {
714 float axis[3], q1[4], dvec[3], newvec[3];
718 const int event_xy_offset[2] = {
719 event_xy[0] + vod->init.event_xy_offset[0],
720 event_xy[1] + vod->init.event_xy_offset[1],
722 calctrackballvec(&vod->ar->winrct, event_xy_offset, newvec);
725 sub_v3_v3v3(dvec, newvec, vod->init.trackvec);
727 angle = (len_v3(dvec) / (2.0f * TRACKBALLSIZE)) * (float)M_PI;
729 /* Allow for rotation beyond the interval [-pi, pi] */
730 angle = angle_wrap_rad(angle);
732 /* This relation is used instead of the actual angle between vectors
733 * so that the angle of rotation is linearly proportional to
734 * the distance that the mouse is dragged. */
736 cross_v3_v3v3(axis, vod->init.trackvec, newvec);
737 axis_angle_to_quat(q1, axis, angle);
739 mul_qt_qtqt(vod->curr.viewquat, q1, vod->init.quat);
741 viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
744 /* New turntable view code by John Aughey */
745 float quat_local_x[4], quat_global_z[4];
748 const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
751 /* Sensitivity will control how fast the viewport rotates. 0.007 was
752 * obtained experimentally by looking at viewport rotation sensitivities
753 * on other modeling programs. */
754 /* Perhaps this should be a configurable user parameter. */
755 const float sensitivity = 0.007f;
757 /* Get the 3x3 matrix and its inverse from the quaternion */
758 quat_to_mat3(m, vod->curr.viewquat);
759 invert_m3_m3(m_inv, m);
761 /* avoid gimble lock */
763 if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
765 cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
766 if (dot_v3v3(xaxis, m_inv[0]) < 0) {
769 fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
770 fac = fabsf(fac - 0.5f) * 2;
772 interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
775 copy_v3_v3(xaxis, m_inv[0]);
778 copy_v3_v3(xaxis, m_inv[0]);
781 /* Determine the direction of the x vector (for rotating up and down) */
782 /* This can likely be computed directly from the quaternion. */
784 /* Perform the up/down rotation */
785 axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(event_xy[1] - vod->prev.event_xy[1]));
786 mul_qt_qtqt(quat_local_x, vod->curr.viewquat, quat_local_x);
788 /* Perform the orbital rotation */
789 axis_angle_to_quat_single(quat_global_z, 'Z', sensitivity * vod->reverse * (event_xy[0] - vod->prev.event_xy[0]));
790 mul_qt_qtqt(vod->curr.viewquat, quat_local_x, quat_global_z);
792 viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
795 /* avoid precision loss over time */
796 normalize_qt(vod->curr.viewquat);
798 /* use a working copy so view rotation locking doesn't overwrite the locked
799 * rotation back into the view we calculate with */
800 copy_qt_qt(rv3d->viewquat, vod->curr.viewquat);
802 /* check for view snap,
803 * note: don't apply snap to vod->viewquat so the view wont jam up */
804 if (vod->axis_snap) {
805 viewrotate_apply_snap(vod);
807 vod->prev.event_xy[0] = event_xy[0];
808 vod->prev.event_xy[1] = event_xy[1];
810 ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, rv3d);
812 ED_region_tag_redraw(vod->ar);
815 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
817 ViewOpsData *vod = op->customdata;
818 short event_code = VIEW_PASS;
819 bool use_autokey = false;
820 int ret = OPERATOR_RUNNING_MODAL;
822 /* execute the events */
823 if (event->type == MOUSEMOVE) {
824 event_code = VIEW_APPLY;
826 else if (event->type == EVT_MODAL_MAP) {
827 switch (event->val) {
828 case VIEW_MODAL_CONFIRM:
829 event_code = VIEW_CONFIRM;
831 case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
832 vod->axis_snap = true;
833 event_code = VIEW_APPLY;
835 case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
836 vod->axis_snap = false;
837 event_code = VIEW_APPLY;
839 case VIEWROT_MODAL_SWITCH_ZOOM:
840 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
841 event_code = VIEW_CONFIRM;
843 case VIEWROT_MODAL_SWITCH_MOVE:
844 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
845 event_code = VIEW_CONFIRM;
849 else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
850 event_code = VIEW_CONFIRM;
853 if (event_code == VIEW_APPLY) {
854 viewrotate_apply(vod, &event->x);
855 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
859 else if (event_code == VIEW_CONFIRM) {
860 ED_view3d_depth_tag_update(vod->rv3d);
862 ret = OPERATOR_FINISHED;
866 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
869 if (ret & OPERATOR_FINISHED) {
870 viewops_data_free(C, op);
876 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
880 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
882 /* makes op->customdata */
883 viewops_data_alloc(C, op);
884 vod = op->customdata;
886 /* poll should check but in some cases fails, see poll func for details */
887 if (vod->rv3d->viewlock & RV3D_LOCKED) {
888 viewops_data_free(C, op);
889 return OPERATOR_PASS_THROUGH;
892 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
896 viewops_flag_from_prefs() |
897 VIEWOPS_FLAG_PERSP_ENSURE |
898 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
900 if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
901 /* Rotate direction we keep always same */
904 if (event->type == MOUSEPAN) {
905 if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
906 event_xy[0] = 2 * event->x - event->prevx;
907 event_xy[1] = 2 * event->y - event->prevy;
910 event_xy[0] = event->prevx;
911 event_xy[1] = event->prevy;
915 /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
916 event_xy[0] = event->prevx;
917 event_xy[1] = event->y;
920 viewrotate_apply(vod, event_xy);
921 ED_view3d_depth_tag_update(vod->rv3d);
923 viewops_data_free(C, op);
925 return OPERATOR_FINISHED;
928 /* add temp handler */
929 WM_event_add_modal_handler(C, op);
931 return OPERATOR_RUNNING_MODAL;
935 /* test for unlocked camera view in quad view */
936 static bool view3d_camera_user_poll(bContext *C)
941 if (ED_view3d_context_user_region(C, &v3d, &ar)) {
942 RegionView3D *rv3d = ar->regiondata;
943 if (rv3d->persp == RV3D_CAMOB) {
951 static bool view3d_lock_poll(bContext *C)
953 View3D *v3d = CTX_wm_view3d(C);
955 RegionView3D *rv3d = CTX_wm_region_view3d(C);
957 return ED_view3d_offset_lock_check(v3d, rv3d);
963 static void viewrotate_cancel(bContext *C, wmOperator *op)
965 viewops_data_free(C, op);
968 void VIEW3D_OT_rotate(wmOperatorType *ot)
971 ot->name = "Rotate View";
972 ot->description = "Rotate the view";
973 ot->idname = "VIEW3D_OT_rotate";
976 ot->invoke = viewrotate_invoke;
977 ot->modal = viewrotate_modal;
978 ot->poll = ED_operator_region_view3d_active;
979 ot->cancel = viewrotate_cancel;
982 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
984 view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
989 /* -------------------------------------------------------------------- */
990 /** \name NDOF Utility Functions
993 #ifdef WITH_INPUT_NDOF
994 #define NDOF_HAS_TRANSLATE ((!ED_view3d_offset_lock_check(v3d, rv3d)) && !is_zero_v3(ndof->tvec))
995 #define NDOF_HAS_ROTATE (((rv3d->viewlock & RV3D_LOCKED) == 0) && !is_zero_v3(ndof->rvec))
998 * \param depth_pt: A point to calculate the depth (in perspective mode)
1000 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1002 float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1004 if (rv3d->is_persp) {
1005 speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1011 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1016 BLI_assert(dist >= 0.0f);
1018 copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1019 /* rv3d->viewinv isn't always valid */
1021 mul_mat3_m4_v3(rv3d->viewinv, tvec);
1023 invert_qt_qt_normalized(viewinv, rv3d->viewquat);
1024 mul_qt_v3(viewinv, tvec);
1027 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1030 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1033 negate_v3_v3(tvec, rv3d->ofs);
1035 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1039 * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1041 * \param has_zoom zoom, otherwise dolly, often `!rv3d->is_persp` since it doesn't make sense to dolly in ortho.
1043 static void view3d_ndof_pan_zoom(
1044 const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1045 const bool has_translate, const bool has_zoom)
1047 RegionView3D *rv3d = ar->regiondata;
1051 if (has_translate == false && has_zoom == false) {
1055 WM_event_ndof_pan_get(ndof, pan_vec, false);
1061 * velocity should be proportional to the linear velocity attained by rotational motion of same strength
1063 * proportional to arclength = radius * angle
1068 /* "zoom in" or "translate"? depends on zoom mode in user settings? */
1069 if (ndof->tvec[2]) {
1070 float zoom_distance = rv3d->dist * ndof->dt * ndof->tvec[2];
1072 if (U.ndof_flag & NDOF_ZOOM_INVERT)
1073 zoom_distance = -zoom_distance;
1075 rv3d->dist += zoom_distance;
1081 /* all callers must check */
1082 if (has_translate) {
1083 BLI_assert(ED_view3d_offset_lock_check((View3D *)sa->spacedata.first, rv3d) == false);
1087 if (has_translate) {
1088 const float speed = view3d_ndof_pan_speed_calc(rv3d);
1090 mul_v3_fl(pan_vec, speed * ndof->dt);
1092 /* transform motion from view to world coordinates */
1093 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1094 mul_qt_v3(view_inv, pan_vec);
1096 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1097 sub_v3_v3(rv3d->ofs, pan_vec);
1099 if (rv3d->viewlock & RV3D_BOXVIEW) {
1100 view3d_boxview_sync(sa, ar);
1106 static void view3d_ndof_orbit(
1107 const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1108 ViewOpsData *vod, const bool apply_dyn_ofs)
1110 View3D *v3d = sa->spacedata.first;
1111 RegionView3D *rv3d = ar->regiondata;
1115 BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1117 ED_view3d_persp_ensure(vod->depsgraph, v3d, ar);
1119 rv3d->view = RV3D_VIEW_USER;
1121 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1123 if (U.ndof_flag & NDOF_TURNTABLE) {
1126 /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1127 float angle, quat[4];
1128 float xvec[3] = {1, 0, 0};
1130 /* only use XY, ignore Z */
1131 WM_event_ndof_rotate_get(ndof, rot);
1133 /* Determine the direction of the x vector (for rotating up and down) */
1134 mul_qt_v3(view_inv, xvec);
1136 /* Perform the up/down rotation */
1137 angle = ndof->dt * rot[0];
1138 axis_angle_to_quat(quat, xvec, angle);
1139 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1141 /* Perform the orbital rotation */
1142 angle = ndof->dt * rot[1];
1144 /* update the onscreen doo-dad */
1145 rv3d->rot_angle = angle;
1146 rv3d->rot_axis[0] = 0;
1147 rv3d->rot_axis[1] = 0;
1148 rv3d->rot_axis[2] = 1;
1150 axis_angle_to_quat_single(quat, 'Z', angle);
1151 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1156 float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1158 /* transform rotation axis from view to world coordinates */
1159 mul_qt_v3(view_inv, axis);
1161 /* update the onscreen doo-dad */
1162 rv3d->rot_angle = angle;
1163 copy_v3_v3(rv3d->rot_axis, axis);
1165 axis_angle_to_quat(quat, axis, angle);
1167 /* apply rotation */
1168 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1171 if (apply_dyn_ofs) {
1172 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1177 * Called from both fly mode and walk mode,
1179 void view3d_ndof_fly(
1180 const wmNDOFMotionData *ndof,
1181 View3D *v3d, RegionView3D *rv3d,
1182 const bool use_precision, const short protectflag,
1183 bool *r_has_translate, bool *r_has_rotate)
1185 bool has_translate = NDOF_HAS_TRANSLATE;
1186 bool has_rotate = NDOF_HAS_ROTATE;
1189 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1191 rv3d->rot_angle = 0.0f; /* disable onscreen rotation doo-dad */
1193 if (has_translate) {
1194 /* ignore real 'dist' since fly has its own speed settings,
1195 * also its overwritten at this point. */
1196 float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1197 float trans[3], trans_orig_y;
1202 WM_event_ndof_pan_get(ndof, trans, false);
1203 mul_v3_fl(trans, speed * ndof->dt);
1204 trans_orig_y = trans[1];
1206 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1210 /* transform motion from view to world coordinates */
1211 mul_qt_v3(view_inv, trans);
1213 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1214 /* replace world z component with device y (yes it makes sense) */
1215 trans[2] = trans_orig_y;
1218 if (rv3d->persp == RV3D_CAMOB) {
1219 /* respect camera position locks */
1220 if (protectflag & OB_LOCK_LOCX) trans[0] = 0.0f;
1221 if (protectflag & OB_LOCK_LOCY) trans[1] = 0.0f;
1222 if (protectflag & OB_LOCK_LOCZ) trans[2] = 0.0f;
1225 if (!is_zero_v3(trans)) {
1226 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1227 sub_v3_v3(rv3d->ofs, trans);
1228 has_translate = true;
1231 has_translate = false;
1236 const float turn_sensitivity = 1.0f;
1240 float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1242 if (fabsf(angle) > 0.0001f) {
1248 /* transform rotation axis from view to world coordinates */
1249 mul_qt_v3(view_inv, axis);
1251 /* apply rotation to view */
1252 axis_angle_to_quat(rotation, axis, angle);
1253 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1255 if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1256 /* force an upright viewpoint
1257 * TODO: make this less... sudden */
1258 float view_horizon[3] = {1.0f, 0.0f, 0.0f}; /* view +x */
1259 float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1261 /* find new inverse since viewquat has changed */
1262 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1263 /* could apply reverse rotation to existing view_inv to save a few cycles */
1265 /* transform view vectors to world coordinates */
1266 mul_qt_v3(view_inv, view_horizon);
1267 mul_qt_v3(view_inv, view_direction);
1270 /* find difference between view & world horizons
1271 * true horizon lives in world xy plane, so look only at difference in z */
1272 angle = -asinf(view_horizon[2]);
1274 /* rotate view so view horizon = world horizon */
1275 axis_angle_to_quat(rotation, view_direction, angle);
1276 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1279 rv3d->view = RV3D_VIEW_USER;
1286 *r_has_translate = has_translate;
1287 *r_has_rotate = has_rotate;
1292 /* -------------------------------------------------------------------- */
1293 /** \name NDOF Operators
1295 * - "orbit" navigation (trackball/turntable)
1297 * - panning in rotationally-locked views
1300 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1302 if (event->type != NDOF_MOTION) {
1303 return OPERATOR_CANCELLED;
1306 const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1311 const wmNDOFMotionData *ndof = event->customdata;
1313 viewops_data_alloc(C, op);
1314 viewops_data_create(
1316 viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1317 vod = op->customdata;
1319 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1324 /* off by default, until changed later this function */
1325 rv3d->rot_angle = 0.0f;
1327 ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1329 if (ndof->progress != P_FINISHING) {
1330 const bool has_rotation = NDOF_HAS_ROTATE;
1331 /* if we can't rotate, fallback to translate (locked axis views) */
1332 const bool has_translate = NDOF_HAS_TRANSLATE && (rv3d->viewlock & RV3D_LOCKED);
1333 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1335 if (has_translate || has_zoom) {
1336 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1340 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1344 ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1346 ED_region_tag_redraw(vod->ar);
1348 viewops_data_free(C, op);
1350 return OPERATOR_FINISHED;
1354 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1357 ot->name = "NDOF Orbit View";
1358 ot->description = "Orbit the view using the 3D mouse";
1359 ot->idname = "VIEW3D_OT_ndof_orbit";
1362 ot->invoke = ndof_orbit_invoke;
1363 ot->poll = ED_operator_view3d_active;
1369 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1371 if (event->type != NDOF_MOTION) {
1372 return OPERATOR_CANCELLED;
1375 const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1380 const wmNDOFMotionData *ndof = event->customdata;
1382 viewops_data_alloc(C, op);
1383 viewops_data_create(
1385 viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1387 vod = op->customdata;
1389 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1394 /* off by default, until changed later this function */
1395 rv3d->rot_angle = 0.0f;
1397 ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1399 if (ndof->progress == P_FINISHING) {
1402 else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1403 /* if we can't rotate, fallback to translate (locked axis views) */
1404 const bool has_translate = NDOF_HAS_TRANSLATE;
1405 const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1407 if (has_translate || has_zoom) {
1408 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, true);
1411 else if ((U.ndof_flag & NDOF_MODE_ORBIT) ||
1412 ED_view3d_offset_lock_check(v3d, rv3d))
1414 const bool has_rotation = NDOF_HAS_ROTATE;
1415 const bool has_zoom = (ndof->tvec[2] != 0.0f);
1418 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, false, has_zoom);
1422 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1425 else { /* free/explore (like fly mode) */
1426 const bool has_rotation = NDOF_HAS_ROTATE;
1427 const bool has_translate = NDOF_HAS_TRANSLATE;
1428 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1432 if (has_translate || has_zoom) {
1433 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1436 dist_backup = rv3d->dist;
1437 ED_view3d_distance_set(rv3d, 0.0f);
1440 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, false);
1443 ED_view3d_distance_set(rv3d, dist_backup);
1446 ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1448 ED_region_tag_redraw(vod->ar);
1450 viewops_data_free(C, op);
1452 return OPERATOR_FINISHED;
1456 void VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType *ot)
1459 ot->name = "NDOF Orbit View with Zoom";
1460 ot->description = "Orbit and zoom the view using the 3D mouse";
1461 ot->idname = "VIEW3D_OT_ndof_orbit_zoom";
1464 ot->invoke = ndof_orbit_zoom_invoke;
1465 ot->poll = ED_operator_view3d_active;
1471 /* -- "pan" navigation
1474 static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1476 if (event->type != NDOF_MOTION) {
1477 return OPERATOR_CANCELLED;
1480 const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1481 View3D *v3d = CTX_wm_view3d(C);
1482 RegionView3D *rv3d = CTX_wm_region_view3d(C);
1483 const wmNDOFMotionData *ndof = event->customdata;
1485 const bool has_translate = NDOF_HAS_TRANSLATE;
1486 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1488 /* we're panning here! so erase any leftover rotation from other operators */
1489 rv3d->rot_angle = 0.0f;
1491 if (!(has_translate || has_zoom))
1492 return OPERATOR_CANCELLED;
1494 ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1496 if (ndof->progress != P_FINISHING) {
1497 ScrArea *sa = CTX_wm_area(C);
1498 ARegion *ar = CTX_wm_region(C);
1500 if (has_translate || has_zoom) {
1501 view3d_ndof_pan_zoom(ndof, sa, ar, has_translate, has_zoom);
1505 ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1507 ED_region_tag_redraw(CTX_wm_region(C));
1509 return OPERATOR_FINISHED;
1513 void VIEW3D_OT_ndof_pan(struct wmOperatorType *ot)
1516 ot->name = "NDOF Pan View";
1517 ot->description = "Pan the view with the 3D mouse";
1518 ot->idname = "VIEW3D_OT_ndof_pan";
1521 ot->invoke = ndof_pan_invoke;
1522 ot->poll = ED_operator_view3d_active;
1530 * wraps #ndof_orbit_zoom but never restrict to orbit.
1532 static int ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1534 /* weak!, but it works */
1535 const int ndof_flag = U.ndof_flag;
1538 U.ndof_flag &= ~NDOF_MODE_ORBIT;
1540 ret = ndof_orbit_zoom_invoke(C, op, event);
1542 U.ndof_flag = ndof_flag;
1547 void VIEW3D_OT_ndof_all(struct wmOperatorType *ot)
1550 ot->name = "NDOF Pan View";
1551 ot->description = "Pan and rotate the view with the 3D mouse";
1552 ot->idname = "VIEW3D_OT_ndof_all";
1555 ot->invoke = ndof_all_invoke;
1556 ot->poll = ED_operator_view3d_active;
1562 #endif /* WITH_INPUT_NDOF */
1566 /* -------------------------------------------------------------------- */
1567 /** \name View Move (Pan) Operator
1570 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
1572 /* called in transform_ops.c, on each regeneration of keymaps */
1573 void viewmove_modal_keymap(wmKeyConfig *keyconf)
1575 static const EnumPropertyItem modal_items[] = {
1576 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1578 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
1579 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1581 {0, NULL, 0, NULL, NULL}
1584 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Move Modal");
1586 /* this function is called for each spacetype, only needs to add map once */
1587 if (keymap && keymap->modal_items) return;
1589 keymap = WM_modalkeymap_add(keyconf, "View3D Move Modal", modal_items);
1591 /* items for modal map */
1592 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1593 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1595 /* disabled mode switching for now, can re-implement better, later on */
1597 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1598 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1599 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1602 /* assign map to operators */
1603 WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
1607 static void viewmove_apply(ViewOpsData *vod, int x, int y)
1609 if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) {
1610 vod->rv3d->ofs_lock[0] -= ((vod->prev.event_xy[0] - x) * 2.0f) / (float)vod->ar->winx;
1611 vod->rv3d->ofs_lock[1] -= ((vod->prev.event_xy[1] - y) * 2.0f) / (float)vod->ar->winy;
1613 else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) {
1614 const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1615 vod->rv3d->camdx += (vod->prev.event_xy[0] - x) / (vod->ar->winx * zoomfac);
1616 vod->rv3d->camdy += (vod->prev.event_xy[1] - y) / (vod->ar->winy * zoomfac);
1617 CLAMP(vod->rv3d->camdx, -1.0f, 1.0f);
1618 CLAMP(vod->rv3d->camdy, -1.0f, 1.0f);
1624 mval_f[0] = x - vod->prev.event_xy[0];
1625 mval_f[1] = y - vod->prev.event_xy[1];
1626 ED_view3d_win_to_delta(vod->ar, mval_f, dvec, vod->init.zfac);
1628 add_v3_v3(vod->rv3d->ofs, dvec);
1630 if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
1631 view3d_boxview_sync(vod->sa, vod->ar);
1635 vod->prev.event_xy[0] = x;
1636 vod->prev.event_xy[1] = y;
1638 ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
1640 ED_region_tag_redraw(vod->ar);
1644 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
1647 ViewOpsData *vod = op->customdata;
1648 short event_code = VIEW_PASS;
1649 bool use_autokey = false;
1650 int ret = OPERATOR_RUNNING_MODAL;
1652 /* execute the events */
1653 if (event->type == MOUSEMOVE) {
1654 event_code = VIEW_APPLY;
1656 else if (event->type == EVT_MODAL_MAP) {
1657 switch (event->val) {
1658 case VIEW_MODAL_CONFIRM:
1659 event_code = VIEW_CONFIRM;
1661 case VIEWROT_MODAL_SWITCH_ZOOM:
1662 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1663 event_code = VIEW_CONFIRM;
1665 case VIEWROT_MODAL_SWITCH_ROTATE:
1666 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
1667 event_code = VIEW_CONFIRM;
1671 else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
1672 event_code = VIEW_CONFIRM;
1675 if (event_code == VIEW_APPLY) {
1676 viewmove_apply(vod, event->x, event->y);
1677 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1681 else if (event_code == VIEW_CONFIRM) {
1682 ED_view3d_depth_tag_update(vod->rv3d);
1684 ret = OPERATOR_FINISHED;
1688 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
1691 if (ret & OPERATOR_FINISHED) {
1692 viewops_data_free(C, op);
1698 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1702 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
1704 /* makes op->customdata */
1705 viewops_data_alloc(C, op);
1706 viewops_data_create(
1708 viewops_flag_from_prefs() |
1709 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
1710 vod = op->customdata;
1712 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1714 if (event->type == MOUSEPAN) {
1715 /* invert it, trackpad scroll follows same principle as 2d windows this way */
1716 viewmove_apply(vod, 2 * event->x - event->prevx, 2 * event->y - event->prevy);
1717 ED_view3d_depth_tag_update(vod->rv3d);
1719 viewops_data_free(C, op);
1721 return OPERATOR_FINISHED;
1724 /* add temp handler */
1725 WM_event_add_modal_handler(C, op);
1727 return OPERATOR_RUNNING_MODAL;
1731 static void viewmove_cancel(bContext *C, wmOperator *op)
1733 viewops_data_free(C, op);
1736 void VIEW3D_OT_move(wmOperatorType *ot)
1740 ot->name = "Pan View";
1741 ot->description = "Move the view";
1742 ot->idname = "VIEW3D_OT_move";
1745 ot->invoke = viewmove_invoke;
1746 ot->modal = viewmove_modal;
1747 ot->poll = ED_operator_view3d_active;
1748 ot->cancel = viewmove_cancel;
1751 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
1754 view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
1759 /* -------------------------------------------------------------------- */
1760 /** \name View Zoom Operator
1763 /* viewdolly_modal_keymap has an exact copy of this, apply fixes to both */
1764 /* called in transform_ops.c, on each regeneration of keymaps */
1765 void viewzoom_modal_keymap(wmKeyConfig *keyconf)
1767 static const EnumPropertyItem modal_items[] = {
1768 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1770 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1771 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
1773 {0, NULL, 0, NULL, NULL}
1776 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Zoom Modal");
1778 /* this function is called for each spacetype, only needs to add map once */
1779 if (keymap && keymap->modal_items) return;
1781 keymap = WM_modalkeymap_add(keyconf, "View3D Zoom Modal", modal_items);
1783 /* disabled mode switching for now, can re-implement better, later on */
1785 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1786 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1787 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
1790 /* assign map to operators */
1791 WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
1795 * \param zoom_xy: Optionally zoom to window location (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1797 static void view_zoom_to_window_xy_camera(
1798 Scene *scene, Depsgraph *depsgraph, View3D *v3d,
1799 ARegion *ar, float dfac, const int zoom_xy[2])
1801 RegionView3D *rv3d = ar->regiondata;
1802 const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
1803 const float zoomfac_new = clamp_f(zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
1804 const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
1807 if (zoom_xy != NULL) {
1809 rctf camera_frame_old;
1810 rctf camera_frame_new;
1812 const float pt_src[2] = {zoom_xy[0], zoom_xy[1]};
1816 ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &camera_frame_old, false);
1817 BLI_rctf_translate(&camera_frame_old, ar->winrct.xmin, ar->winrct.ymin);
1819 rv3d->camzoom = camzoom_new;
1820 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1822 ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &camera_frame_new, false);
1823 BLI_rctf_translate(&camera_frame_new, ar->winrct.xmin, ar->winrct.ymin);
1825 BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
1826 sub_v2_v2v2(delta_px, pt_dst, pt_src);
1828 /* translate the camera offset using pixel space delta
1829 * mapped back to the camera (same logic as panning in camera view) */
1830 zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
1832 rv3d->camdx += delta_px[0] / (ar->winx * zoomfac_px);
1833 rv3d->camdy += delta_px[1] / (ar->winy * zoomfac_px);
1834 CLAMP(rv3d->camdx, -1.0f, 1.0f);
1835 CLAMP(rv3d->camdy, -1.0f, 1.0f);
1838 rv3d->camzoom = camzoom_new;
1839 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1844 * \param zoom_xy: Optionally zoom to window location (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1846 static void view_zoom_to_window_xy_3d(ARegion *ar, float dfac, const int zoom_xy[2])
1848 RegionView3D *rv3d = ar->regiondata;
1849 const float dist_new = rv3d->dist * dfac;
1851 if (zoom_xy != NULL) {
1859 negate_v3_v3(tpos, rv3d->ofs);
1861 mval_f[0] = (float)(((zoom_xy[0] - ar->winrct.xmin) * 2) - ar->winx) / 2.0f;
1862 mval_f[1] = (float)(((zoom_xy[1] - ar->winrct.ymin) * 2) - ar->winy) / 2.0f;
1864 /* Project cursor position into 3D space */
1865 zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
1866 ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
1868 /* Calculate view target position for dolly */
1869 add_v3_v3v3(tvec, tpos, dvec);
1872 /* Offset to target position and dolly */
1873 copy_v3_v3(rv3d->ofs, tvec);
1874 rv3d->dist = dist_new;
1876 /* Calculate final offset */
1877 madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
1880 rv3d->dist = dist_new;
1884 static float viewzoom_scale_value(
1886 const short viewzoom,
1887 const bool zoom_invert, const bool zoom_invert_force,
1888 const int xy_curr[2], const int xy_init[2],
1889 const float val, const float val_orig,
1890 double *r_timer_lastdraw)
1894 if (viewzoom == USER_ZOOM_CONT) {
1895 double time = PIL_check_seconds_timer();
1896 float time_step = (float)(time - *r_timer_lastdraw);
1899 if (U.uiflag & USER_ZOOM_HORIZ) {
1900 fac = (float)(xy_init[0] - xy_curr[0]);
1903 fac = (float)(xy_init[1] - xy_curr[1]);
1906 if (zoom_invert != zoom_invert_force) {
1911 zfac = 1.0f + ((fac / 20.0f) * time_step);
1912 *r_timer_lastdraw = time;
1914 else if (viewzoom == USER_ZOOM_SCALE) {
1915 /* method which zooms based on how far you move the mouse */
1917 const int ctr[2] = {
1918 BLI_rcti_cent_x(winrct),
1919 BLI_rcti_cent_y(winrct),
1921 float len_new = 5 + len_v2v2_int(ctr, xy_curr);
1922 float len_old = 5 + len_v2v2_int(ctr, xy_init);
1924 /* intentionally ignore 'zoom_invert' for scale */
1925 if (zoom_invert_force) {
1926 SWAP(float, len_new, len_old);
1929 zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
1931 else { /* USER_ZOOM_DOLLY */
1935 if (U.uiflag & USER_ZOOM_HORIZ) {
1936 len_new += (winrct->xmax - (xy_curr[0]));
1937 len_old += (winrct->xmax - (xy_init[0]));
1940 len_new += (winrct->ymax - (xy_curr[1]));
1941 len_old += (winrct->ymax - (xy_init[1]));
1944 if (zoom_invert != zoom_invert_force) {
1945 SWAP(float, len_new, len_old);
1948 zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
1955 static float viewzoom_scale_value_offset(
1957 const short viewzoom,
1958 const bool zoom_invert, const bool zoom_invert_force,
1959 const int xy_curr[2], const int xy_init[2], const int xy_offset[2],
1960 const float val, const float val_orig,
1961 double *r_timer_lastdraw)
1963 const int xy_curr_offset[2] = {
1964 xy_curr[0] + xy_offset[0],
1965 xy_curr[1] + xy_offset[1],
1967 const int xy_init_offset[2] = {
1968 xy_init[0] + xy_offset[0],
1969 xy_init[1] + xy_offset[1],
1971 return viewzoom_scale_value(
1972 winrct, viewzoom, zoom_invert, zoom_invert_force,
1973 xy_curr_offset, xy_init_offset,
1974 val, val_orig, r_timer_lastdraw);
1977 static void viewzoom_apply_camera(
1978 ViewOpsData *vod, const int xy[2],
1979 const short viewzoom, const bool zoom_invert, const bool zoom_to_pos)
1982 float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->init.camzoom) * 2.0f;
1983 float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1985 zfac = viewzoom_scale_value_offset(
1986 &vod->ar->winrct, viewzoom, zoom_invert, true,
1987 xy, vod->init.event_xy, vod->init.event_xy_offset,
1988 zoomfac, zoomfac_prev,
1991 if (zfac != 1.0f && zfac != 0.0f) {
1992 /* calculate inverted, then invert again (needed because of camera zoom scaling) */
1994 view_zoom_to_window_xy_camera(
1995 vod->scene, vod->depsgraph, vod->v3d,
1996 vod->ar, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
1999 ED_region_tag_redraw(vod->ar);
2002 static void viewzoom_apply_3d(
2003 ViewOpsData *vod, const int xy[2],
2004 const short viewzoom, const bool zoom_invert, const bool zoom_to_pos)
2007 float dist_range[2];
2009 ED_view3d_dist_range_get(vod->v3d, dist_range);
2011 zfac = viewzoom_scale_value_offset(
2012 &vod->ar->winrct, viewzoom, zoom_invert, false,
2013 xy, vod->init.event_xy, vod->init.event_xy_offset,
2014 vod->rv3d->dist, vod->init.dist,
2018 const float zfac_min = dist_range[0] / vod->rv3d->dist;
2019 const float zfac_max = dist_range[1] / vod->rv3d->dist;
2020 CLAMP(zfac, zfac_min, zfac_max);
2022 view_zoom_to_window_xy_3d(
2023 vod->ar, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
2026 /* these limits were in old code too */
2027 CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2029 if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2030 view3d_boxview_sync(vod->sa, vod->ar);
2033 ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2035 ED_region_tag_redraw(vod->ar);
2038 static void viewzoom_apply(
2039 ViewOpsData *vod, const int xy[2],
2040 const short viewzoom, const bool zoom_invert, const bool zoom_to_pos)
2042 if ((vod->rv3d->persp == RV3D_CAMOB) &&
2043 (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0)
2045 viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2048 viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2052 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2054 ViewOpsData *vod = op->customdata;
2055 short event_code = VIEW_PASS;
2056 bool use_autokey = false;
2057 int ret = OPERATOR_RUNNING_MODAL;
2059 /* execute the events */
2060 if (event->type == TIMER && event->customdata == vod->timer) {
2061 /* continuous zoom */
2062 event_code = VIEW_APPLY;
2064 else if (event->type == MOUSEMOVE) {
2065 event_code = VIEW_APPLY;
2067 else if (event->type == EVT_MODAL_MAP) {
2068 switch (event->val) {
2069 case VIEW_MODAL_CONFIRM:
2070 event_code = VIEW_CONFIRM;
2072 case VIEWROT_MODAL_SWITCH_MOVE:
2073 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2074 event_code = VIEW_CONFIRM;
2076 case VIEWROT_MODAL_SWITCH_ROTATE:
2077 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2078 event_code = VIEW_CONFIRM;
2082 else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2083 event_code = VIEW_CONFIRM;
2086 if (event_code == VIEW_APPLY) {
2087 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2089 vod, &event->x, U.viewzoom,
2090 (U.uiflag & USER_ZOOM_INVERT) != 0,
2091 (use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2092 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2096 else if (event_code == VIEW_CONFIRM) {
2097 ED_view3d_depth_tag_update(vod->rv3d);
2099 ret = OPERATOR_FINISHED;
2103 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2106 if (ret & OPERATOR_FINISHED) {
2107 viewops_data_free(C, op);
2113 static int viewzoom_exec(bContext *C, wmOperator *op)
2115 Depsgraph *depsgraph = CTX_data_depsgraph(C);
2116 Scene *scene = CTX_data_scene(C);
2122 float dist_range[2];
2124 const int delta = RNA_int_get(op->ptr, "delta");
2125 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2127 if (op->customdata) {
2128 ViewOpsData *vod = op->customdata;
2134 sa = CTX_wm_area(C);
2135 ar = CTX_wm_region(C);
2138 v3d = sa->spacedata.first;
2139 rv3d = ar->regiondata;
2142 use_cam_zoom = (rv3d->persp == RV3D_CAMOB) && !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2145 const int *zoom_xy = NULL;
2146 if (use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
2147 zoom_xy_buf[0] = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") : ar->winx / 2;
2148 zoom_xy_buf[1] = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") : ar->winy / 2;
2149 zoom_xy = zoom_xy_buf;
2152 ED_view3d_dist_range_get(v3d, dist_range);
2155 const float step = 1.2f;
2156 /* this min and max is also in viewmove() */
2158 view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2161 if (rv3d->dist < dist_range[1]) {
2162 view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2167 const float step = 1.0f / 1.2f;
2169 view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2172 if (rv3d->dist > dist_range[0]) {
2173 view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2178 if (rv3d->viewlock & RV3D_BOXVIEW) {
2179 view3d_boxview_sync(sa, ar);
2182 ED_view3d_depth_tag_update(rv3d);
2184 ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
2185 ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2187 ED_region_tag_redraw(ar);
2189 viewops_data_free(C, op);
2191 return OPERATOR_FINISHED;
2194 /* viewdolly_invoke() copied this function, changes here may apply there */
2195 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2199 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2201 /* makes op->customdata */
2202 viewops_data_alloc(C, op);
2203 viewops_data_create(
2205 viewops_flag_from_prefs() |
2206 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2207 vod = op->customdata;
2209 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2211 /* if one or the other zoom position aren't set, set from event */
2212 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2213 RNA_int_set(op->ptr, "mx", event->x);
2214 RNA_int_set(op->ptr, "my", event->y);
2217 if (RNA_struct_property_is_set(op->ptr, "delta")) {
2218 viewzoom_exec(C, op);
2221 if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2223 if (U.uiflag & USER_ZOOM_HORIZ) {
2224 vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2227 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2228 vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x - event->prevx;
2231 vod, &event->prevx, USER_ZOOM_DOLLY,
2232 (U.uiflag & USER_ZOOM_INVERT) != 0,
2233 (use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2234 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2236 ED_view3d_depth_tag_update(vod->rv3d);
2238 viewops_data_free(C, op);
2239 return OPERATOR_FINISHED;
2242 if (U.viewzoom == USER_ZOOM_CONT) {
2243 /* needs a timer to continue redrawing */
2244 vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2245 vod->prev.time = PIL_check_seconds_timer();
2248 /* add temp handler */
2249 WM_event_add_modal_handler(C, op);
2251 return OPERATOR_RUNNING_MODAL;
2254 return OPERATOR_FINISHED;
2257 static void viewzoom_cancel(bContext *C, wmOperator *op)
2259 viewops_data_free(C, op);
2262 void VIEW3D_OT_zoom(wmOperatorType *ot)
2265 ot->name = "Zoom View";
2266 ot->description = "Zoom in/out in the view";
2267 ot->idname = "VIEW3D_OT_zoom";
2270 ot->invoke = viewzoom_invoke;
2271 ot->exec = viewzoom_exec;
2272 ot->modal = viewzoom_modal;
2273 ot->poll = ED_operator_region_view3d_active;
2274 ot->cancel = viewzoom_cancel;
2277 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2280 view3d_operator_properties_common(
2282 V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2287 /* -------------------------------------------------------------------- */
2288 /** \name View Dolly Operator
2290 * Like zoom but translates the view offset along the view direction
2291 * which avoids #RegionView3D.dist approaching zero.
2294 /* this is an exact copy of viewzoom_modal_keymap */
2295 /* called in transform_ops.c, on each regeneration of keymaps */
2296 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2298 static const EnumPropertyItem modal_items[] = {
2299 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2301 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2302 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2304 {0, NULL, 0, NULL, NULL}
2307 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Dolly Modal");
2309 /* this function is called for each spacetype, only needs to add map once */
2310 if (keymap && keymap->modal_items) return;
2312 keymap = WM_modalkeymap_add(keyconf, "View3D Dolly Modal", modal_items);
2314 /* disabled mode switching for now, can re-implement better, later on */
2316 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2317 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2318 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2321 /* assign map to operators */
2322 WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2325 static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
2327 View3D *v3d = CTX_wm_view3d(C);
2328 RegionView3D *rv3d = CTX_wm_region_view3d(C);
2329 if (ED_view3d_offset_lock_check(v3d, rv3d)) {
2330 BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
2338 static void view_dolly_to_vector_3d(ARegion *ar, float orig_ofs[3], float dvec[3], float dfac)
2340 RegionView3D *rv3d = ar->regiondata;
2341 madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2344 static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_invert)
2351 if (U.uiflag & USER_ZOOM_HORIZ) {
2352 len1 = (vod->ar->winrct.xmax - xy[0]) + 5;
2353 len2 = (vod->ar->winrct.xmax - vod->init.event_xy[0]) + 5;
2356 len1 = (vod->ar->winrct.ymax - xy[1]) + 5;
2357 len2 = (vod->ar->winrct.ymax - vod->init.event_xy[1]) + 5;
2360 SWAP(float, len1, len2);
2363 zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2367 view_dolly_to_vector_3d(vod->ar, vod->init.ofs, vod->init.mousevec, zfac);
2370 if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2371 view3d_boxview_sync(vod->sa, vod->ar);
2374 ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2376 ED_region_tag_redraw(vod->ar);
2380 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2382 ViewOpsData *vod = op->customdata;
2383 short event_code = VIEW_PASS;
2384 bool use_autokey = false;
2385 int ret = OPERATOR_RUNNING_MODAL;
2387 /* execute the events */
2388 if (event->type == MOUSEMOVE) {
2389 event_code = VIEW_APPLY;
2391 else if (event->type == EVT_MODAL_MAP) {
2392 switch (event->val) {
2393 case VIEW_MODAL_CONFIRM:
2394 event_code = VIEW_CONFIRM;
2396 case VIEWROT_MODAL_SWITCH_MOVE:
2397 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2398 event_code = VIEW_CONFIRM;
2400 case VIEWROT_MODAL_SWITCH_ROTATE:
2401 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2402 event_code = VIEW_CONFIRM;
2406 else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2407 event_code = VIEW_CONFIRM;
2410 if (event_code == VIEW_APPLY) {
2411 viewdolly_apply(vod, &event->x, (U.uiflag & USER_ZOOM_INVERT) != 0);
2412 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2416 else if (event_code == VIEW_CONFIRM) {
2417 ED_view3d_depth_tag_update(vod->rv3d);
2419 ret = OPERATOR_FINISHED;
2423 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2426 if (ret & OPERATOR_FINISHED) {
2427 viewops_data_free(C, op);
2433 static int viewdolly_exec(bContext *C, wmOperator *op)
2441 const int delta = RNA_int_get(op->ptr, "delta");
2443 if (op->customdata) {
2444 ViewOpsData *vod = op->customdata;
2448 copy_v3_v3(mousevec, vod->init.mousevec);
2451 sa = CTX_wm_area(C);
2452 ar = CTX_wm_region(C);
2453 negate_v3_v3(mousevec, ((RegionView3D *)ar->regiondata)->viewinv[2]);
2454 normalize_v3(mousevec);
2457 v3d = sa->spacedata.first;
2458 rv3d = ar->regiondata;
2460 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2462 /* overwrite the mouse vector with the view direction (zoom into the center) */
2463 if ((use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2464 normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2467 view_dolly_to_vector_3d(ar, rv3d->ofs, mousevec, delta < 0 ? 0.2f : 1.8f);
2469 if (rv3d->viewlock & RV3D_BOXVIEW) {
2470 view3d_boxview_sync(sa, ar);
2473 ED_view3d_depth_tag_update(rv3d);
2475 ED_view3d_camera_lock_sync(CTX_data_depsgraph(C), v3d, rv3d);
2477 ED_region_tag_redraw(ar);
2479 viewops_data_free(C, op);
2481 return OPERATOR_FINISHED;
2484 /* copied from viewzoom_invoke(), changes here may apply there */
2485 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2489 if (viewdolly_offset_lock_check(C, op))
2490 return OPERATOR_CANCELLED;
2492 /* makes op->customdata */
2493 viewops_data_alloc(C, op);
2494 vod = op->customdata;
2496 /* poll should check but in some cases fails, see poll func for details */
2497 if (vod->rv3d->viewlock & RV3D_LOCKED) {
2498 viewops_data_free(C, op);
2499 return OPERATOR_PASS_THROUGH;
2502 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2504 /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2505 /* switch from camera view when: */
2506 if (vod->rv3d->persp != RV3D_PERSP) {
2507 if (vod->rv3d->persp == RV3D_CAMOB) {
2508 /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2509 const Depsgraph *depsgraph = CTX_data_depsgraph(C);
2510 ED_view3d_persp_switch_from_camera(depsgraph, vod->v3d, vod->rv3d, RV3D_PERSP);
2513 vod->rv3d->persp = RV3D_PERSP;
2515 ED_region_tag_redraw(vod->ar);
2518 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2520 viewops_data_create(
2522 viewops_flag_from_prefs() |
2523 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2526 /* if one or the other zoom position aren't set, set from event */
2527 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2528 RNA_int_set(op->ptr, "mx", event->x);
2529 RNA_int_set(op->ptr, "my", event->y);
2532 if (RNA_struct_property_is_set(op->ptr, "delta")) {
2533 viewdolly_exec(C, op);
2536 /* overwrite the mouse vector with the view direction (zoom into the center) */
2537 if ((use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2538 negate_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
2539 normalize_v3(vod->init.mousevec);
2542 if (event->type == MOUSEZOOM) {
2543 /* Bypass Zoom invert flag for track pads (pass false always) */
2545 if (U.uiflag & USER_ZOOM_HORIZ) {
2546 vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2549 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2550 vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x - event->prevx;
2552 viewdolly_apply(vod, &event->prevx, (U.uiflag & USER_ZOOM_INVERT) == 0);
2553 ED_view3d_depth_tag_update(vod->rv3d);
2555 viewops_data_free(C, op);
2556 return OPERATOR_FINISHED;
2559 /* add temp handler */
2560 WM_event_add_modal_handler(C, op);
2562 return OPERATOR_RUNNING_MODAL;
2565 return OPERATOR_FINISHED;
2568 static void viewdolly_cancel(bContext *C, wmOperator *op)
2570 viewops_data_free(C, op);
2573 void VIEW3D_OT_dolly(wmOperatorType *ot)
2576 ot->name = "Dolly View";
2577 ot->description = "Dolly in/out in the view";
2578 ot->idname = "VIEW3D_OT_dolly";
2581 ot->invoke = viewdolly_invoke;
2582 ot->exec = viewdolly_exec;
2583 ot->modal = viewdolly_modal;
2584 ot->poll = ED_operator_region_view3d_active;
2585 ot->cancel = viewdolly_cancel;
2588 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2591 view3d_operator_properties_common(
2592 ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2597 /* -------------------------------------------------------------------- */
2598 /** \name View All Operator
2600 * Move & Zoom the view to fit all of it's contents.
2603 static void view3d_from_minmax(
2604 bContext *C, View3D *v3d, ARegion *ar,
2605 const float min[3], const float max[3],
2606 bool ok_dist, const int smooth_viewtx)
2608 RegionView3D *rv3d = ar->regiondata;
2612 ED_view3d_smooth_view_force_finish(C, v3d, ar);
2618 sub_v3_v3v3(afm, max, min);
2619 size = max_fff(afm[0], afm[1], afm[2]);
2624 if (rv3d->is_persp) {
2625 if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2633 if (size < 0.0001f) {
2634 /* bounding box was a single point so do not zoom */
2638 /* adjust zoom so it looks nicer */
2644 new_dist = ED_view3d_radius_to_dist(v3d, ar, CTX_data_depsgraph(C), persp, true, (size / 2) * VIEW3D_MARGIN);
2645 if (rv3d->is_persp) {
2646 /* don't zoom closer than the near clipping plane */
2647 new_dist = max_ff(new_dist, v3d->near * 1.5f);
2652 mid_v3_v3v3(new_ofs, min, max);
2655 if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2656 rv3d->persp = RV3D_PERSP;
2657 ED_view3d_smooth_view(
2658 C, v3d, ar, smooth_viewtx,
2659 &(const V3D_SmoothParams) {
2660 .camera_old = v3d->camera, .ofs = new_ofs,
2661 .dist = ok_dist ? &new_dist : NULL});
2664 ED_view3d_smooth_view(
2665 C, v3d, ar, smooth_viewtx,
2666 &(const V3D_SmoothParams) {.ofs = new_ofs, .dist = ok_dist ? &new_dist : NULL});
2669 /* smooth view does viewlock RV3D_BOXVIEW copy */
2673 * Same as #view3d_from_minmax but for all regions (except cameras).
2675 static void view3d_from_minmax_multi(
2676 bContext *C, View3D *v3d,
2677 const float min[3], const float max[3],
2678 const bool ok_dist, const int smooth_viewtx)
2680 ScrArea *sa = CTX_wm_area(C);
2682 for (ar = sa->regionbase.first; ar; ar = ar->next) {
2683 if (ar->regiontype == RGN_TYPE_WINDOW) {
2684 RegionView3D *rv3d = ar->regiondata;
2685 /* when using all regions, don't jump out of camera view,
2686 * but _do_ allow locked cameras to be moved */
2687 if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2688 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2694 static int view3d_all_exec(bContext *C, wmOperator *op)
2696 ARegion *ar = CTX_wm_region(C);
2697 View3D *v3d = CTX_wm_view3d(C);
2698 Scene *scene = CTX_data_scene(C);
2699 const Depsgraph *depsgraph = CTX_data_depsgraph(C);
2700 ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2702 const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2703 const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2704 /* any one of the regions may be locked */
2705 (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2706 const bool center = RNA_boolean_get(op->ptr, "center");
2707 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2709 float min[3], max[3];
2710 bool changed = false;
2713 /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2714 View3DCursor *cursor = ED_view3d_cursor3d_get(scene, v3d);
2717 zero_v3(cursor->location);
2718 unit_qt(cursor->rotation);
2721 INIT_MINMAX(min, max);
2724 for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2725 if (BASE_VISIBLE(base_eval)) {
2728 Object *ob = DEG_get_original_object(base_eval->object);
2729 if (skip_camera && ob == v3d->camera) {
2733 BKE_object_minmax(base_eval->object, min, max, false);
2737 ED_region_tag_redraw(ar);
2738 /* TODO - should this be cancel?
2739 * I think no, because we always move the cursor, with or without
2740 * object, but in this case there is no change in the scene,
2741 * only the cursor so I choice a ED_region_tag like
2742 * view3d_smooth_view do for the center_cursor.
2745 return OPERATOR_FINISHED;
2748 if (use_all_regions) {
2749 view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
2752 view3d_from_minmax(C, v3d, ar, min, max, true, smooth_viewtx);
2756 DEG_id_tag_update(&scene->id, DEG_TAG_COPY_ON_WRITE);
2759 return OPERATOR_FINISHED;
2763 void VIEW3D_OT_view_all(wmOperatorType *ot)
2766 ot->name = "View All";
2767 ot->description = "View all objects in scene";
2768 ot->idname = "VIEW3D_OT_view_all";
2771 ot->exec = view3d_all_exec;
2772 ot->poll = ED_operator_region_view3d_active;
2778 view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
2779 RNA_def_boolean(ot->srna, "center", 0, "Center", "");
2784 /* -------------------------------------------------------------------- */
2785 /** \name View Selected Operator
2787 * Move & Zoom the view to fit selected contents.
2790 /* like a localview without local!, was centerview() in 2.4x */
2791 static int viewselected_exec(bContext *C, wmOperator *op)
2793 ARegion *ar = CTX_wm_region(C);
2794 View3D *v3d = CTX_wm_view3d(C);
2795 Scene *scene = CTX_data_scene(C);
2796 Depsgraph *depsgraph = CTX_data_depsgraph(C);
2797 ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2798 bGPdata *gpd = CTX_data_gpencil_data(C);
2799 const bool is_gp_edit = GPENCIL_ANY_MODE(gpd);
2800 const bool is_face_map = ((is_gp_edit == false) && ar->gizmo_map &&
2801 WM_gizmomap_is_any_selected(ar->gizmo_map));
2802 Object *ob_eval = OBACT(view_layer_eval);
2803 Object *obedit = CTX_data_edit_object(C);
2804 float min[3], max[3];
2805 bool ok = false, ok_dist = true;
2806 const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2807 const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2808 /* any one of the regions may be locked */
2809 (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2810 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2812 INIT_MINMAX(min, max);
2817 if (ob_eval && (ob_eval->mode & OB_MODE_WEIGHT_PAINT)) {
2818 /* hard-coded exception, we look for the one selected armature */
2819 /* this is weak code this way, we should make a generic active/selection callback interface once... */
2821 for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2822 if (TESTBASELIB(base_eval)) {
2823 if (base_eval->object->type == OB_ARMATURE)
2824 if (base_eval->object->mode & OB_MODE_POSE)
2829 ob_eval = base_eval->object;
2833 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
2835 /* we're only interested in selected points here... */
2836 if ((gps->flag & GP_STROKE_SELECT) && (gps->flag & GP_STROKE_3DSPACE)) {
2837 ok |= BKE_gpencil_stroke_minmax(gps, true, min, max);
2842 if ((ob_eval) && (ok)) {
2843 add_v3_v3(min, ob_eval->obmat[3]);
2844 add_v3_v3(max, ob_eval->obmat[3]);
2847 else if (ob_eval && (ob_eval->type == OB_GPENCIL)) {
2848 ok |= BKE_gpencil_data_minmax(ob_eval, gpd, min, max);
2850 else if (is_face_map) {
2851 ok = WM_gizmomap_minmax(ar->gizmo_map, true, true, min, max);
2855 FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, obedit->mode, ob_eval_iter) {
2856 ok |= ED_view3d_minmax_verts(ob_eval_iter, min, max);
2858 FOREACH_OBJECT_IN_MODE_END;
2860 else if (ob_eval && (ob_eval->mode & OB_MODE_POSE)) {
2861 FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, ob_eval->mode, ob_eval_iter) {
2862 ok |= BKE_pose_minmax(ob_eval_iter, min, max, true, true);
2864 FOREACH_OBJECT_IN_MODE_END;
2866 else if (BKE_paint_select_face_test(ob_eval)) {
2867 ok = paintface_minmax(ob_eval, min, max);
2869 else if (ob_eval && (ob_eval->mode & OB_MODE_PARTICLE_EDIT)) {
2870 ok = PE_minmax(scene, view_layer_eval, min, max);
2873 (ob_eval->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)))
2875 BKE_paint_stroke_get_average(scene, ob_eval, min);
2876 copy_v3_v3(max, min);
2878 ok_dist = 0; /* don't zoom */
2882 for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
2883 if (TESTBASE(base_eval)) {
2885 if (skip_camera && base_eval->object == v3d->camera) {
2889 /* account for duplis */
2890 if (BKE_object_minmax_dupli(depsgraph, scene, base_eval->object, min, max, false) == 0)
2891 BKE_object_minmax(base_eval->object, min, max, false); /* use if duplis not found */
2899 return OPERATOR_FINISHED;
2902 if (use_all_regions) {
2903 view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
2906 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2909 return OPERATOR_FINISHED;
2912 void VIEW3D_OT_view_selected(wmOperatorType *ot)
2915 ot->name = "View Selected";
2916 ot->description = "Move the view to the selection center";
2917 ot->idname = "VIEW3D_OT_view_selected";
2920 ot->exec = viewselected_exec;
2921 ot->poll = ED_operator_region_view3d_active;
2927 view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
2932 /* -------------------------------------------------------------------- */
2933 /** \name View Lock Clear Operator
2936 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
2938 View3D *v3d = CTX_wm_view3d(C);
2941 ED_view3d_lock_clear(v3d);
2943 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
2945 return OPERATOR_FINISHED;
2948 return OPERATOR_CANCELLED;
2952 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
2956 ot->name = "View Lock Clear";
2957 ot->description = "Clear all view locking";
2958 ot->idname = "VIEW3D_OT_view_lock_clear";
2961 ot->exec = view_lock_clear_exec;
2962 ot->poll = ED_operator_region_view3d_active;
2970 /* -------------------------------------------------------------------- */
2971 /** \name View Lock to Active Operator
2974 static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op))
2976 View3D *v3d = CTX_wm_view3d(C);
2977 Object *obact = CTX_data_active_object(C);
2980 ED_view3d_lock_clear(v3d);
2982 v3d->ob_centre = obact; /* can be NULL */
2984 if (obact && obact->type == OB_ARMATURE) {
2985 if (obact->mode & OB_MODE_POSE) {
2986 Object *obact_eval = DEG_get_evaluated_object(CTX_data_depsgraph(C), obact);
2987 bPoseChannel *pcham_act = BKE_pose_channel_active(obact_eval);
2989 BLI_strncpy(v3d->ob_centre_bone, pcham_act->name, sizeof(v3d->ob_centre_bone));
2993 EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone;
2995 BLI_strncpy(v3d->ob_centre_bone, ebone_act->name, sizeof(v3d->ob_centre_bone));
3000 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3002 return OPERATOR_FINISHED;
3005 return OPERATOR_CANCELLED;
3009 void VIEW3D_OT_view_lock_to_active(wmOperatorType *ot)
3013 ot->name = "View Lock to Active";
3014 ot->description = "Lock the view to the active object/bone";
3015 ot->idname = "VIEW3D_OT_view_lock_to_active";
3018 ot->exec = view_lock_to_active_exec;
3019 ot->poll = ED_operator_region_view3d_active;
3027 /* -------------------------------------------------------------------- */
3028 /** \name View Center Cursor Operator
3031 static int viewcenter_cursor_exec(bContext *C, wmOperator *op)
3033 View3D *v3d = CTX_wm_view3d(C);
3034 RegionView3D *rv3d = CTX_wm_region_view3d(C);
3035 Scene *scene = CTX_data_scene(C);
3038 ARegion *ar = CTX_wm_region(C);
3039 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3041 ED_view3d_smooth_view_force_finish(C, v3d, ar);
3043 /* non camera center */
3045 negate_v3_v3(new_ofs, ED_view3d_cursor3d_get(scene, v3d)->location);
3046 ED_view3d_smooth_view(
3047 C, v3d, ar, smooth_viewtx,
3048 &(const V3D_SmoothParams) {.ofs = new_ofs});
3050 /* smooth view does viewlock RV3D_BOXVIEW copy */
3053 return OPERATOR_FINISHED;
3056 void VIEW3D_OT_view_center_cursor(wmOperatorType *ot)
3059 ot->name = "Center View to Cursor";
3060 ot->description = "Center the view so that the cursor is in the middle of the view";
3061 ot->idname = "VIEW3D_OT_view_center_cursor";
3064 ot->exec = viewcenter_cursor_exec;
3065 ot->poll = ED_operator_view3d_active;
3073 /* -------------------------------------------------------------------- */
3074 /** \name View Center Pick Operator
3077 static int viewcenter_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3079 View3D *v3d = CTX_wm_view3d(C);
3080 RegionView3D *rv3d = CTX_wm_region_view3d(C);
3081 ARegion *ar = CTX_wm_region(C);
3084 struct Depsgraph *depsgraph = CTX_data_depsgraph(C);
3086 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3088 ED_view3d_smooth_view_force_finish(C, v3d, ar);
3090 view3d_operator_needs_opengl(C);
3092 if (ED_view3d_autodist(depsgraph, ar, v3d, event->mval, new_ofs, false, NULL)) {
3096 /* fallback to simple pan */
3097 negate_v3_v3(new_ofs, rv3d->ofs);
3098 ED_view3d_win_to_3d_int(v3d, ar, new_ofs, event->mval, new_ofs);
3101 ED_view3d_smooth_view(
3102 C, v3d, ar, smooth_viewtx,
3103 &(const V3D_SmoothParams) {.ofs = new_ofs});
3106 return OPERATOR_FINISHED;
3109 void VIEW3D_OT_view_center_pick(wmOperatorType *ot)
3112 ot->name = "Center View to Mouse";
3113 ot->description = "Center the view to the Z-depth position under the mouse cursor";
3114 ot->idname = "VIEW3D_OT_view_center_pick";
3117 ot->invoke = viewcenter_pick_invoke;
3118 ot->poll = ED_operator_view3d_active;
3126 /* -------------------------------------------------------------------- */
3127 /** \name View Camera Center Operator
3130 static int view3d_center_camera_exec(bContext *C, wmOperator *UNUSED(op)) /* was view3d_home() in 2.4x */
3132 Depsgraph *depsgraph = CTX_data_depsgraph(C);
3133 Scene *scene = CTX_data_scene(C);
3141 /* no NULL check is needed, poll checks */
3142 ED_view3d_context_user_region(C, &v3d, &ar);
3143 rv3d = ar->regiondata;
3145 rv3d->camdx = rv3d->camdy = 0.0f;
3147 ED_view3d_calc_camera_border_size(scene, depsgraph, ar, v3d, rv3d, size);
3149 /* 4px is just a little room from the edge of the area */
3150 xfac = (float)ar->winx / (float)(size[0] + 4);
3151 yfac = (float)ar->winy / (float)(size[1] + 4);
3153 rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(min_ff(xfac, yfac));
3154 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
3156 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3158 return OPERATOR_FINISHED;
3161 void VIEW3D_OT_view_center_camera(wmOperatorType *ot)
3164 ot->name = "View Camera Center";
3165 ot->description = "Center the camera view";
3166 ot->idname = "VIEW3D_OT_view_center_camera";
3169 ot->exec = view3d_center_camera_exec;
3170 ot->poll = view3d_camera_user_poll;
3178 /* -------------------------------------------------------------------- */
3179 /** \name View Lock Center Operator
3182 static int view3d_center_lock_exec(bContext *C, wmOperator *UNUSED(op)) /* was view3d_home() in 2.4x */
3184 RegionView3D *rv3d = CTX_wm_region_view3d(C);
3186 zero_v2(rv3d->ofs_lock);
3188 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C));
3190 return OPERATOR_FINISHED;
3193 void VIEW3D_OT_view_center_lock(wmOperatorType *ot)
3196 ot->name = "View Lock Center";
3197 ot->description = "Center the view lock offset";
3198 ot->idname = "VIEW3D_OT_view_center_lock";
3201 ot->exec = view3d_center_lock_exec;
3202 ot->poll = view3d_lock_poll;
3210 /* -------------------------------------------------------------------- */
3211 /** \name Set Render Border Operator
3214 static int render_border_exec(bContext *C, wmOperator *op)
3216 View3D *v3d = CTX_wm_view3d(C);
3217 ARegion *ar = CTX_wm_region(C);
3218 RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3220 Scene *scene = CTX_data_scene(C);
3225 /* get box select values using rna */
3226 WM_operator_properties_border_to_rcti(op, &rect);
3228 /* calculate range */
3230 if (rv3d->persp == RV3D_CAMOB) {
3231 Depsgraph *depsgraph = CTX_data_depsgraph(C);
3232 ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &vb, false);