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
36 #include "DNA_armature_types.h"
37 #include "DNA_curve_types.h"
38 #include "DNA_object_types.h"
39 #include "DNA_scene_types.h"
41 #include "MEM_guardedalloc.h"
43 #include "BLI_blenlib.h"
45 #include "BLI_utildefines.h"
47 #include "BKE_camera.h"
48 #include "BKE_context.h"
50 #include "BKE_library.h"
51 #include "BKE_object.h"
52 #include "BKE_paint.h"
53 #include "BKE_report.h"
54 #include "BKE_scene.h"
55 #include "BKE_screen.h"
56 #include "BKE_action.h"
57 #include "BKE_depsgraph.h" /* for ED_view3d_camera_lock_sync */
61 #include "BIF_glutil.h"
66 #include "RNA_access.h"
67 #include "RNA_define.h"
69 #include "ED_armature.h"
70 #include "ED_particle.h"
71 #include "ED_keyframing.h"
72 #include "ED_screen.h"
73 #include "ED_transform.h"
75 #include "ED_view3d.h"
77 #include "UI_resources.h"
79 #include "PIL_time.h" /* smoothview */
81 #include "view3d_intern.h" /* own include */
83 bool ED_view3d_offset_lock_check(const View3D *v3d, const RegionView3D *rv3d)
85 return (rv3d->persp != RV3D_CAMOB) && (v3d->ob_centre_cursor || v3d->ob_centre);
88 static bool view3d_operator_offset_lock_check(bContext *C, wmOperator *op)
90 View3D *v3d = CTX_wm_view3d(C);
91 RegionView3D *rv3d = CTX_wm_region_view3d(C);
92 if (ED_view3d_offset_lock_check(v3d, rv3d)) {
93 BKE_report(op->reports, RPT_WARNING, "View offset is locked");
101 /* ********************** view3d_edit: view manipulations ********************* */
104 * \return true when the view-port is locked to its camera.
106 bool ED_view3d_camera_lock_check(const View3D *v3d, const RegionView3D *rv3d)
108 return ((v3d->camera) &&
109 (v3d->camera->id.lib == NULL) &&
110 (v3d->flag2 & V3D_LOCK_CAMERA) &&
111 (rv3d->persp == RV3D_CAMOB));
115 * Apply the camera object transformation to the view-port.
116 * (needed so we can use regular view-port manipulation operators, that sync back to the camera).
118 void ED_view3d_camera_lock_init_ex(View3D *v3d, RegionView3D *rv3d, const bool calc_dist)
120 if (ED_view3d_camera_lock_check(v3d, rv3d)) {
122 /* using a fallback dist is OK here since ED_view3d_from_object() compensates for it */
123 rv3d->dist = ED_view3d_offset_distance(v3d->camera->obmat, rv3d->ofs, VIEW3D_DIST_FALLBACK);
125 ED_view3d_from_object(v3d->camera, rv3d->ofs, rv3d->viewquat, &rv3d->dist, NULL);
129 void ED_view3d_camera_lock_init(View3D *v3d, RegionView3D *rv3d)
131 ED_view3d_camera_lock_init_ex(v3d, rv3d, true);
135 * Apply the view-port transformation back to the camera object.
137 * \return true if the camera is moved.
139 bool ED_view3d_camera_lock_sync(View3D *v3d, RegionView3D *rv3d)
141 if (ED_view3d_camera_lock_check(v3d, rv3d)) {
142 ObjectTfmProtectedChannels obtfm;
145 if ((U.uiflag & USER_CAM_LOCK_NO_PARENT) == 0 && (root_parent = v3d->camera->parent)) {
149 float view_mat[4][4];
150 float diff_mat[4][4];
151 float parent_mat[4][4];
153 while (root_parent->parent) {
154 root_parent = root_parent->parent;
157 ED_view3d_to_m4(view_mat, rv3d->ofs, rv3d->viewquat, rv3d->dist);
159 normalize_m4_m4(tmat, v3d->camera->obmat);
161 invert_m4_m4(imat, tmat);
162 mul_m4_m4m4(diff_mat, view_mat, imat);
164 mul_m4_m4m4(parent_mat, diff_mat, root_parent->obmat);
166 BKE_object_tfm_protected_backup(root_parent, &obtfm);
167 BKE_object_apply_mat4(root_parent, parent_mat, true, false);
168 BKE_object_tfm_protected_restore(root_parent, &obtfm, root_parent->protectflag);
170 ob_update = v3d->camera;
172 DAG_id_tag_update(&ob_update->id, OB_RECALC_OB);
173 WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, ob_update);
174 ob_update = ob_update->parent;
178 /* always maintain the same scale */
179 const short protect_scale_all = (OB_LOCK_SCALEX | OB_LOCK_SCALEY | OB_LOCK_SCALEZ);
180 BKE_object_tfm_protected_backup(v3d->camera, &obtfm);
181 ED_view3d_to_object(v3d->camera, rv3d->ofs, rv3d->viewquat, rv3d->dist);
182 BKE_object_tfm_protected_restore(v3d->camera, &obtfm, v3d->camera->protectflag | protect_scale_all);
184 DAG_id_tag_update(&v3d->camera->id, OB_RECALC_OB);
185 WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, v3d->camera);
195 bool ED_view3d_camera_autokey(
196 Scene *scene, ID *id_key,
197 struct bContext *C, const bool do_rotate, const bool do_translate)
199 if (autokeyframe_cfra_can_key(scene, id_key)) {
200 const float cfra = (float)CFRA;
201 ListBase dsources = {NULL, NULL};
203 /* add data-source override for the camera object */
204 ANIM_relative_keyingset_add_source(&dsources, id_key, NULL, NULL);
207 * 1) on the first frame
208 * 2) on each subsequent frame
209 * TODO: need to check in future that frame changed before doing this
212 struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_ROTATION_ID);
213 ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cfra);
216 struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_LOCATION_ID);
217 ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cfra);
221 BLI_freelistN(&dsources);
231 * Call after modifying a locked view.
233 * \note Not every view edit currently auto-keys (numpad for eg),
234 * this is complicated because of smoothview.
236 bool ED_view3d_camera_lock_autokey(
237 View3D *v3d, RegionView3D *rv3d,
238 struct bContext *C, const bool do_rotate, const bool do_translate)
240 /* similar to ED_view3d_cameracontrol_update */
241 if (ED_view3d_camera_lock_check(v3d, rv3d)) {
242 Scene *scene = CTX_data_scene(C);
245 if ((U.uiflag & USER_CAM_LOCK_NO_PARENT) == 0 && (root_parent = v3d->camera->parent)) {
246 while (root_parent->parent) {
247 root_parent = root_parent->parent;
249 id_key = &root_parent->id;
252 id_key = &v3d->camera->id;
255 return ED_view3d_camera_autokey(scene, id_key, C, do_rotate, do_translate);
263 * For viewport operators that exit camera persp.
265 * \note This differs from simply setting ``rv3d->persp = persp`` because it
266 * sets the ``ofs`` and ``dist`` values of the viewport so it matches the camera,
267 * otherwise switching out of camera view may jump to a different part of the scene.
269 static void view3d_persp_switch_from_camera(View3D *v3d, RegionView3D *rv3d, const char persp)
271 BLI_assert(rv3d->persp == RV3D_CAMOB);
272 BLI_assert(persp != RV3D_CAMOB);
275 rv3d->dist = ED_view3d_offset_distance(v3d->camera->obmat, rv3d->ofs, VIEW3D_DIST_FALLBACK);
276 ED_view3d_from_object(v3d->camera, rv3d->ofs, rv3d->viewquat, &rv3d->dist, NULL);
279 if (!ED_view3d_camera_lock_check(v3d, rv3d)) {
284 /* ********************* box view support ***************** */
286 static void view3d_boxview_clip(ScrArea *sa)
289 BoundBox *bb = MEM_callocN(sizeof(BoundBox), "clipbb");
291 float x1 = 0.0f, y1 = 0.0f, z1 = 0.0f, ofs[3] = {0.0f, 0.0f, 0.0f};
294 /* create bounding box */
295 for (ar = sa->regionbase.first; ar; ar = ar->next) {
296 if (ar->regiontype == RGN_TYPE_WINDOW) {
297 RegionView3D *rv3d = ar->regiondata;
299 if (rv3d->viewlock & RV3D_BOXCLIP) {
300 if (ELEM(rv3d->view, RV3D_VIEW_TOP, RV3D_VIEW_BOTTOM)) {
301 if (ar->winx > ar->winy) x1 = rv3d->dist;
302 else x1 = ar->winx * rv3d->dist / ar->winy;
304 if (ar->winx > ar->winy) y1 = ar->winy * rv3d->dist / ar->winx;
305 else y1 = rv3d->dist;
306 copy_v2_v2(ofs, rv3d->ofs);
308 else if (ELEM(rv3d->view, RV3D_VIEW_FRONT, RV3D_VIEW_BACK)) {
309 ofs[2] = rv3d->ofs[2];
311 if (ar->winx > ar->winy) z1 = ar->winy * rv3d->dist / ar->winx;
312 else z1 = rv3d->dist;
318 for (val = 0; val < 8; val++) {
319 if (ELEM(val, 0, 3, 4, 7))
320 bb->vec[val][0] = -x1 - ofs[0];
322 bb->vec[val][0] = x1 - ofs[0];
324 if (ELEM(val, 0, 1, 4, 5))
325 bb->vec[val][1] = -y1 - ofs[1];
327 bb->vec[val][1] = y1 - ofs[1];
330 bb->vec[val][2] = -z1 - ofs[2];
332 bb->vec[val][2] = z1 - ofs[2];
335 /* normals for plane equations */
336 normal_tri_v3(clip[0], bb->vec[0], bb->vec[1], bb->vec[4]);
337 normal_tri_v3(clip[1], bb->vec[1], bb->vec[2], bb->vec[5]);
338 normal_tri_v3(clip[2], bb->vec[2], bb->vec[3], bb->vec[6]);
339 normal_tri_v3(clip[3], bb->vec[3], bb->vec[0], bb->vec[7]);
340 normal_tri_v3(clip[4], bb->vec[4], bb->vec[5], bb->vec[6]);
341 normal_tri_v3(clip[5], bb->vec[0], bb->vec[2], bb->vec[1]);
343 /* then plane equations */
344 for (val = 0; val < 6; val++) {
345 clip[val][3] = -dot_v3v3(clip[val], bb->vec[val % 5]);
348 /* create bounding box */
349 for (ar = sa->regionbase.first; ar; ar = ar->next) {
350 if (ar->regiontype == RGN_TYPE_WINDOW) {
351 RegionView3D *rv3d = ar->regiondata;
353 if (rv3d->viewlock & RV3D_BOXCLIP) {
354 rv3d->rflag |= RV3D_CLIPPING;
355 memcpy(rv3d->clip, clip, sizeof(clip));
356 if (rv3d->clipbb) MEM_freeN(rv3d->clipbb);
357 rv3d->clipbb = MEM_dupallocN(bb);
365 * Find which axis values are shared between both views and copy to \a rv3d_dst
366 * taking axis flipping into account.
368 static void view3d_boxview_sync_axis(RegionView3D *rv3d_dst, RegionView3D *rv3d_src)
370 /* absolute axis values above this are considered to be set (will be ~1.0f) */
371 const float axis_eps = 0.5f;
374 /* use the view rotation to identify which axis to sync on */
375 float view_axis_all[4][3] = {
381 float *view_src_x = &view_axis_all[0][0];
382 float *view_src_y = &view_axis_all[1][0];
384 float *view_dst_x = &view_axis_all[2][0];
385 float *view_dst_y = &view_axis_all[3][0];
389 /* we could use rv3d->viewinv, but better not depend on view matrix being updated */
390 if (UNLIKELY(ED_view3d_quat_from_axis_view(rv3d_src->view, viewinv) == false)) {
394 mul_qt_v3(viewinv, view_src_x);
395 mul_qt_v3(viewinv, view_src_y);
397 if (UNLIKELY(ED_view3d_quat_from_axis_view(rv3d_dst->view, viewinv) == false)) {
401 mul_qt_v3(viewinv, view_dst_x);
402 mul_qt_v3(viewinv, view_dst_y);
404 /* check source and dest have a matching axis */
405 for (i = 0; i < 3; i++) {
406 if (((fabsf(view_src_x[i]) > axis_eps) || (fabsf(view_src_y[i]) > axis_eps)) &&
407 ((fabsf(view_dst_x[i]) > axis_eps) || (fabsf(view_dst_y[i]) > axis_eps)))
409 rv3d_dst->ofs[i] = rv3d_src->ofs[i];
414 /* sync center/zoom view of region to others, for view transforms */
415 static void view3d_boxview_sync(ScrArea *sa, ARegion *ar)
418 RegionView3D *rv3d = ar->regiondata;
421 for (artest = sa->regionbase.first; artest; artest = artest->next) {
422 if (artest != ar && artest->regiontype == RGN_TYPE_WINDOW) {
423 RegionView3D *rv3dtest = artest->regiondata;
425 if (rv3dtest->viewlock & RV3D_LOCKED) {
426 rv3dtest->dist = rv3d->dist;
427 view3d_boxview_sync_axis(rv3dtest, rv3d);
428 clip |= rv3dtest->viewlock & RV3D_BOXCLIP;
430 ED_region_tag_redraw(artest);
436 view3d_boxview_clip(sa);
440 /* for home, center etc */
441 void view3d_boxview_copy(ScrArea *sa, ARegion *ar)
444 RegionView3D *rv3d = ar->regiondata;
447 for (artest = sa->regionbase.first; artest; artest = artest->next) {
448 if (artest != ar && artest->regiontype == RGN_TYPE_WINDOW) {
449 RegionView3D *rv3dtest = artest->regiondata;
451 if (rv3dtest->viewlock) {
452 rv3dtest->dist = rv3d->dist;
453 copy_v3_v3(rv3dtest->ofs, rv3d->ofs);
454 ED_region_tag_redraw(artest);
456 clip |= ((rv3dtest->viewlock & RV3D_BOXCLIP) != 0);
462 view3d_boxview_clip(sa);
466 /* 'clip' is used to know if our clip setting has changed */
467 void ED_view3d_quadview_update(ScrArea *sa, ARegion *ar, bool do_clip)
469 ARegion *ar_sync = NULL;
470 RegionView3D *rv3d = ar->regiondata;
472 /* this function copies flags from the first of the 3 other quadview
473 * regions to the 2 other, so it assumes this is the region whose
474 * properties are always being edited, weak */
475 viewlock = rv3d->viewlock;
477 if ((viewlock & RV3D_LOCKED) == 0) {
478 do_clip = (viewlock & RV3D_BOXCLIP) != 0;
481 else if ((viewlock & RV3D_BOXVIEW) == 0 && (viewlock & RV3D_BOXCLIP) != 0) {
483 viewlock &= ~RV3D_BOXCLIP;
486 for (; ar; ar = ar->prev) {
487 if (ar->alignment == RGN_ALIGN_QSPLIT) {
488 rv3d = ar->regiondata;
489 rv3d->viewlock = viewlock;
491 if (do_clip && (viewlock & RV3D_BOXCLIP) == 0) {
492 rv3d->rflag &= ~RV3D_BOXCLIP;
495 /* use ar_sync so we sync with one of the aligned views below
496 * else the view jumps on changing view settings like 'clip'
497 * since it copies from the perspective view */
502 if (rv3d->viewlock & RV3D_BOXVIEW) {
503 view3d_boxview_sync(sa, ar_sync ? ar_sync : sa->regionbase.last);
506 /* ensure locked regions have an axis, locked user views don't make much sense */
507 if (viewlock & RV3D_LOCKED) {
508 int index_qsplit = 0;
509 for (ar = sa->regionbase.first; ar; ar = ar->next) {
510 if (ar->alignment == RGN_ALIGN_QSPLIT) {
511 rv3d = ar->regiondata;
512 if (rv3d->viewlock) {
513 if (!RV3D_VIEW_IS_AXIS(rv3d->view)) {
514 rv3d->view = ED_view3d_lock_view_from_index(index_qsplit);
515 rv3d->persp = RV3D_ORTHO;
516 ED_view3d_lock(rv3d);
524 ED_area_tag_redraw(sa);
527 /* ************************** init for view ops **********************************/
529 typedef struct ViewOpsData {
530 /* context pointers (assigned by viewops_data_alloc) */
537 /* needed for continuous zoom */
539 double timer_lastdraw;
542 float viewquat[4]; /* working copy of rv3d->viewquat */
544 float mousevec[3]; /* dolly only */
546 float dist_prev, camzoom_prev;
548 bool axis_snap; /* view rotate only */
551 /* use for orbit selection and auto-dist */
552 float ofs[3], dyn_ofs[3];
555 int origx, origy, oldx, oldy;
556 int origkey; /* the key that triggered the operator */
560 #define TRACKBALLSIZE (1.1)
562 static void calctrackballvec(const rcti *rect, int mx, int my, float vec[3])
564 float x, y, radius, d, z, t;
566 radius = TRACKBALLSIZE;
568 /* normalize x and y */
569 x = BLI_rcti_cent_x(rect) - mx;
570 x /= (float)(BLI_rcti_size_x(rect) / 4);
571 y = BLI_rcti_cent_y(rect) - my;
572 y /= (float)(BLI_rcti_size_y(rect) / 2);
574 d = sqrtf(x * x + y * y);
575 if (d < radius * (float)M_SQRT1_2) { /* Inside sphere */
576 z = sqrtf(radius * radius - d * d);
578 else { /* On hyperbola */
579 t = radius / (float)M_SQRT2;
585 vec[2] = -z; /* yah yah! */
589 /* -------------------------------------------------------------------- */
592 /** \name Generic View Operator Custom-Data.
596 * Allocate and fill in context pointers for #ViewOpsData
598 static void viewops_data_alloc(bContext *C, wmOperator *op)
600 ViewOpsData *vod = MEM_callocN(sizeof(ViewOpsData), "viewops data");
603 op->customdata = vod;
604 vod->scene = CTX_data_scene(C);
605 vod->sa = CTX_wm_area(C);
606 vod->ar = CTX_wm_region(C);
607 vod->v3d = vod->sa->spacedata.first;
608 vod->rv3d = vod->ar->regiondata;
611 static void view3d_orbit_apply_dyn_ofs(
612 float r_ofs[3], const float dyn_ofs[3],
613 const float oldquat[4], const float viewquat[4])
616 conjugate_qt_qt(q1, oldquat);
617 mul_qt_qtqt(q1, q1, viewquat);
619 conjugate_qt(q1); /* conj == inv for unit quat */
621 sub_v3_v3(r_ofs, dyn_ofs);
622 mul_qt_v3(q1, r_ofs);
623 add_v3_v3(r_ofs, dyn_ofs);
626 static bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
628 static float lastofs[3] = {0, 0, 0};
631 Scene *scene = CTX_data_scene(C);
634 if (ob && (ob->mode & OB_MODE_ALL_PAINT) && (BKE_object_pose_armature_get(ob) == NULL)) {
635 /* in case of sculpting use last average stroke position as a rotation
636 * center, in other cases it's not clear what rotation center shall be
637 * so just rotate around object origin
639 if (ob->mode & (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT)) {
641 BKE_paint_stroke_get_average(scene, ob, stroke);
642 copy_v3_v3(lastofs, stroke);
645 copy_v3_v3(lastofs, ob->obmat[3]);
649 else if (ob && (ob->mode & OB_MODE_EDIT) && (ob->type == OB_FONT)) {
650 Curve *cu = ob->data;
651 EditFont *ef = cu->editfont;
655 for (i = 0; i < 4; i++) {
656 add_v2_v2(lastofs, ef->textcurs[i]);
658 mul_v2_fl(lastofs, 1.0f / 4.0f);
660 mul_m4_v3(ob->obmat, lastofs);
664 else if (ob == NULL || ob->mode == OB_MODE_OBJECT) {
665 /* object mode use boundbox centers */
666 View3D *v3d = CTX_wm_view3d(C);
668 unsigned int tot = 0;
669 float select_center[3];
671 zero_v3(select_center);
672 for (base = FIRSTBASE; base; base = base->next) {
673 if (TESTBASE(v3d, base)) {
674 /* use the boundbox if we can */
675 Object *ob = base->object;
677 if (ob->bb && !(ob->bb->flag & BOUNDBOX_DIRTY)) {
680 BKE_boundbox_calc_center_aabb(ob->bb, cent);
682 mul_m4_v3(ob->obmat, cent);
683 add_v3_v3(select_center, cent);
686 add_v3_v3(select_center, ob->obmat[3]);
692 mul_v3_fl(select_center, 1.0f / (float)tot);
693 copy_v3_v3(lastofs, select_center);
698 /* If there's no selection, lastofs is unmodified and last value since static */
699 is_set = calculateTransformCenter(C, V3D_CENTROID, lastofs, NULL);
702 copy_v3_v3(r_dyn_ofs, lastofs);
708 * Calculate the values for #ViewOpsData
710 static void viewops_data_create_ex(bContext *C, wmOperator *op, const wmEvent *event,
711 const bool use_orbit_select,
712 const bool use_orbit_zbuf)
714 ViewOpsData *vod = op->customdata;
715 RegionView3D *rv3d = vod->rv3d;
717 /* set the view from the camera, if view locking is enabled.
718 * we may want to make this optional but for now its needed always */
719 ED_view3d_camera_lock_init(vod->v3d, vod->rv3d);
721 vod->dist_prev = rv3d->dist;
722 vod->camzoom_prev = rv3d->camzoom;
723 copy_qt_qt(vod->viewquat, rv3d->viewquat);
724 copy_qt_qt(vod->oldquat, rv3d->viewquat);
725 vod->origx = vod->oldx = event->x;
726 vod->origy = vod->oldy = event->y;
727 vod->origkey = event->type; /* the key that triggered the operator. */
728 vod->use_dyn_ofs = false;
729 copy_v3_v3(vod->ofs, rv3d->ofs);
731 if (use_orbit_select) {
733 vod->use_dyn_ofs = true;
735 view3d_orbit_calc_center(C, vod->dyn_ofs);
737 negate_v3(vod->dyn_ofs);
739 else if (use_orbit_zbuf) {
740 Scene *scene = CTX_data_scene(C);
741 float fallback_depth_pt[3];
743 view3d_operator_needs_opengl(C); /* needed for zbuf drawing */
745 negate_v3_v3(fallback_depth_pt, rv3d->ofs);
747 if ((vod->use_dyn_ofs = ED_view3d_autodist(scene, vod->ar, vod->v3d,
748 event->mval, vod->dyn_ofs, true, fallback_depth_pt)))
750 if (rv3d->is_persp) {
751 float my_origin[3]; /* original G.vd->ofs */
752 float my_pivot[3]; /* view */
755 /* locals for dist correction */
759 negate_v3_v3(my_origin, rv3d->ofs); /* ofs is flipped */
761 /* Set the dist value to be the distance from this 3d point
762 * this means youll always be able to zoom into it and panning wont go bad when dist was zero */
764 /* remove dist value */
765 upvec[0] = upvec[1] = 0;
766 upvec[2] = rv3d->dist;
767 copy_m3_m4(mat, rv3d->viewinv);
769 mul_m3_v3(mat, upvec);
770 sub_v3_v3v3(my_pivot, rv3d->ofs, upvec);
771 negate_v3(my_pivot); /* ofs is flipped */
773 /* find a new ofs value that is along the view axis (rather than the mouse location) */
774 closest_to_line_v3(dvec, vod->dyn_ofs, my_pivot, my_origin);
775 vod->dist_prev = rv3d->dist = len_v3v3(my_pivot, dvec);
777 negate_v3_v3(rv3d->ofs, dvec);
780 const float mval_ar_mid[2] = {
781 (float)vod->ar->winx / 2.0f,
782 (float)vod->ar->winy / 2.0f};
784 ED_view3d_win_to_3d(vod->ar, vod->dyn_ofs, mval_ar_mid, rv3d->ofs);
785 negate_v3(rv3d->ofs);
787 negate_v3(vod->dyn_ofs);
788 copy_v3_v3(vod->ofs, rv3d->ofs);
794 const float mval_f[2] = {(float)event->mval[0],
795 (float)event->mval[1]};
796 ED_view3d_win_to_vector(vod->ar, mval_f, vod->mousevec);
799 /* lookup, we don't pass on v3d to prevent confusement */
800 vod->grid = vod->v3d->grid;
801 vod->far = vod->v3d->far;
803 calctrackballvec(&vod->ar->winrct, event->x, event->y, vod->trackvec);
807 negate_v3_v3(tvec, rv3d->ofs);
808 vod->zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
812 if (rv3d->persmat[2][1] < 0.0f)
813 vod->reverse = -1.0f;
815 rv3d->rflag |= RV3D_NAVIGATING;
818 static void viewops_data_create(bContext *C, wmOperator *op, const wmEvent *event)
820 viewops_data_create_ex(
822 (U.uiflag & USER_ORBIT_SELECTION) != 0,
823 (U.uiflag & USER_ZBUF_ORBIT) != 0);
826 static void viewops_data_free(bContext *C, wmOperator *op)
829 Paint *p = BKE_paint_get_active_from_context(C);
831 if (op->customdata) {
832 ViewOpsData *vod = op->customdata;
834 vod->rv3d->rflag &= ~RV3D_NAVIGATING;
837 WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
840 op->customdata = NULL;
843 ar = CTX_wm_region(C);
846 if (p && (p->flags & PAINT_FAST_NAVIGATE))
847 ED_region_tag_redraw(ar);
852 /* ************************** viewrotate **********************************/
860 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
861 #define VIEW_MODAL_CONFIRM 1 /* used for all view operations */
862 #define VIEWROT_MODAL_AXIS_SNAP_ENABLE 2
863 #define VIEWROT_MODAL_AXIS_SNAP_DISABLE 3
864 #define VIEWROT_MODAL_SWITCH_ZOOM 4
865 #define VIEWROT_MODAL_SWITCH_MOVE 5
866 #define VIEWROT_MODAL_SWITCH_ROTATE 6
868 /* called in transform_ops.c, on each regeneration of keymaps */
869 void viewrotate_modal_keymap(wmKeyConfig *keyconf)
871 static EnumPropertyItem modal_items[] = {
872 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
874 {VIEWROT_MODAL_AXIS_SNAP_ENABLE, "AXIS_SNAP_ENABLE", 0, "Enable Axis Snap", ""},
875 {VIEWROT_MODAL_AXIS_SNAP_DISABLE, "AXIS_SNAP_DISABLE", 0, "Disable Axis Snap", ""},
877 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
878 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
880 {0, NULL, 0, NULL, NULL}
883 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Rotate Modal");
885 /* this function is called for each spacetype, only needs to add map once */
886 if (keymap && keymap->modal_items) return;
888 keymap = WM_modalkeymap_add(keyconf, "View3D Rotate Modal", modal_items);
890 /* items for modal map */
891 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
892 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
894 WM_modalkeymap_add_item(keymap, LEFTALTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_AXIS_SNAP_ENABLE);
895 WM_modalkeymap_add_item(keymap, LEFTALTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_AXIS_SNAP_DISABLE);
897 /* disabled mode switching for now, can re-implement better, later on */
899 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
900 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
901 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
904 /* assign map to operators */
905 WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
909 static void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat[4])
911 if (vod->use_dyn_ofs) {
912 RegionView3D *rv3d = vod->rv3d;
913 copy_v3_v3(rv3d->ofs, vod->ofs);
914 view3d_orbit_apply_dyn_ofs(rv3d->ofs, vod->dyn_ofs, vod->oldquat, viewquat);
918 static void viewrotate_apply_snap(ViewOpsData *vod)
920 const float axis_limit = DEG2RADF(45 / 3);
922 RegionView3D *rv3d = vod->rv3d;
924 float viewquat_inv[4];
925 float zaxis[3] = {0, 0, 1};
930 invert_qt_qt(viewquat_inv, vod->viewquat);
932 mul_qt_v3(viewquat_inv, zaxis);
936 for (x = -1; x < 2; x++) {
937 for (y = -1; y < 2; y++) {
938 for (z = -1; z < 2; z++) {
940 float zaxis_test[3] = {x, y, z};
942 normalize_v3(zaxis_test);
944 if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
945 copy_v3_v3(zaxis_best, zaxis_test);
955 /* find the best roll */
956 float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
957 float viewquat_align[4]; /* viewquat aligned to zaxis_best */
958 float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
959 float best_angle = axis_limit;
962 /* viewquat_align is the original viewquat aligned to the snapped axis
963 * for testing roll */
964 rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
965 normalize_qt(viewquat_align);
966 mul_qt_qtqt(viewquat_align, vod->viewquat, viewquat_align);
967 normalize_qt(viewquat_align);
968 invert_qt_qt(viewquat_align_inv, viewquat_align);
970 vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
971 invert_qt(quat_snap);
972 normalize_qt(quat_snap);
974 /* check if we can find the roll */
978 for (j = 0; j < 8; j++) {
980 float xaxis1[3] = {1, 0, 0};
981 float xaxis2[3] = {1, 0, 0};
982 float quat_final_inv[4];
984 axis_angle_to_quat(quat_roll, zaxis_best, (float)j * DEG2RADF(45.0f));
985 normalize_qt(quat_roll);
987 mul_qt_qtqt(quat_final, quat_snap, quat_roll);
988 normalize_qt(quat_final);
990 /* compare 2 vector angles to find the least roll */
991 invert_qt_qt(quat_final_inv, quat_final);
992 mul_qt_v3(viewquat_align_inv, xaxis1);
993 mul_qt_v3(quat_final_inv, xaxis2);
994 angle = angle_v3v3(xaxis1, xaxis2);
996 if (angle <= best_angle) {
999 copy_qt_qt(quat_best, quat_final);
1004 /* lock 'quat_best' to an axis view if we can */
1005 rv3d->view = ED_view3d_quat_to_axis_view(quat_best, 0.01f);
1006 if (rv3d->view != RV3D_VIEW_USER) {
1007 ED_view3d_quat_from_axis_view(rv3d->view, quat_best);
1011 copy_qt_qt(quat_best, viewquat_align);
1014 copy_qt_qt(rv3d->viewquat, quat_best);
1016 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1020 static void viewrotate_apply(ViewOpsData *vod, int x, int y)
1022 RegionView3D *rv3d = vod->rv3d;
1024 rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
1026 if (U.flag & USER_TRACKBALL) {
1027 float phi, si, q1[4], dvec[3], newvec[3];
1029 calctrackballvec(&vod->ar->winrct, x, y, newvec);
1031 sub_v3_v3v3(dvec, newvec, vod->trackvec);
1034 si /= (float)(2.0 * TRACKBALLSIZE);
1036 cross_v3_v3v3(q1 + 1, vod->trackvec, newvec);
1037 normalize_v3(q1 + 1);
1039 /* Allow for rotation beyond the interval [-pi, pi] */
1043 /* This relation is used instead of
1044 * - phi = asin(si) so that the angle
1045 * - of rotation is linearly proportional
1046 * - to the distance that the mouse is
1048 phi = si * (float)M_PI_2;
1051 mul_v3_fl(q1 + 1, sinf(phi));
1052 mul_qt_qtqt(vod->viewquat, q1, vod->oldquat);
1054 viewrotate_apply_dyn_ofs(vod, vod->viewquat);
1057 /* New turntable view code by John Aughey */
1058 float quat_local_x[4], quat_global_z[4];
1061 const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
1064 /* Sensitivity will control how fast the viewport rotates. 0.007 was
1065 * obtained experimentally by looking at viewport rotation sensitivities
1066 * on other modeling programs. */
1067 /* Perhaps this should be a configurable user parameter. */
1068 const float sensitivity = 0.007f;
1070 /* Get the 3x3 matrix and its inverse from the quaternion */
1071 quat_to_mat3(m, vod->viewquat);
1072 invert_m3_m3(m_inv, m);
1074 /* avoid gimble lock */
1076 if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
1078 cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
1079 if (dot_v3v3(xaxis, m_inv[0]) < 0) {
1082 fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
1083 fac = fabsf(fac - 0.5f) * 2;
1085 interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
1088 copy_v3_v3(xaxis, m_inv[0]);
1091 copy_v3_v3(xaxis, m_inv[0]);
1094 /* Determine the direction of the x vector (for rotating up and down) */
1095 /* This can likely be computed directly from the quaternion. */
1097 /* Perform the up/down rotation */
1098 axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(y - vod->oldy));
1099 mul_qt_qtqt(quat_local_x, vod->viewquat, quat_local_x);
1101 /* Perform the orbital rotation */
1102 axis_angle_normalized_to_quat(quat_global_z, zvec_global, sensitivity * vod->reverse * (x - vod->oldx));
1103 mul_qt_qtqt(vod->viewquat, quat_local_x, quat_global_z);
1105 viewrotate_apply_dyn_ofs(vod, vod->viewquat);
1108 /* avoid precision loss over time */
1109 normalize_qt(vod->viewquat);
1111 /* use a working copy so view rotation locking doesnt overwrite the locked
1112 * rotation back into the view we calculate with */
1113 copy_qt_qt(rv3d->viewquat, vod->viewquat);
1115 /* check for view snap,
1116 * note: don't apply snap to vod->viewquat so the view wont jam up */
1117 if (vod->axis_snap) {
1118 viewrotate_apply_snap(vod);
1123 ED_view3d_camera_lock_sync(vod->v3d, rv3d);
1125 ED_region_tag_redraw(vod->ar);
1128 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
1130 ViewOpsData *vod = op->customdata;
1131 short event_code = VIEW_PASS;
1132 bool use_autokey = false;
1133 int ret = OPERATOR_RUNNING_MODAL;
1135 /* execute the events */
1136 if (event->type == MOUSEMOVE) {
1137 event_code = VIEW_APPLY;
1139 else if (event->type == EVT_MODAL_MAP) {
1140 switch (event->val) {
1141 case VIEW_MODAL_CONFIRM:
1142 event_code = VIEW_CONFIRM;
1144 case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
1145 vod->axis_snap = true;
1146 event_code = VIEW_APPLY;
1148 case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
1149 vod->axis_snap = false;
1150 event_code = VIEW_APPLY;
1152 case VIEWROT_MODAL_SWITCH_ZOOM:
1153 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1154 event_code = VIEW_CONFIRM;
1156 case VIEWROT_MODAL_SWITCH_MOVE:
1157 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
1158 event_code = VIEW_CONFIRM;
1162 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
1163 event_code = VIEW_CONFIRM;
1166 if (event_code == VIEW_APPLY) {
1167 viewrotate_apply(vod, event->x, event->y);
1168 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1172 else if (event_code == VIEW_CONFIRM) {
1173 ED_view3d_depth_tag_update(vod->rv3d);
1175 ret = OPERATOR_FINISHED;
1179 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
1182 if (ret & OPERATOR_FINISHED) {
1183 viewops_data_free(C, op);
1190 * Action to take when rotating the view,
1191 * handle auto-persp and logic for switching out of views.
1195 static bool view3d_ensure_persp(struct View3D *v3d, ARegion *ar)
1197 RegionView3D *rv3d = ar->regiondata;
1198 const bool autopersp = (U.uiflag & USER_AUTOPERSP) != 0;
1200 BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1202 if (ED_view3d_camera_lock_check(v3d, rv3d))
1205 if (rv3d->persp != RV3D_PERSP) {
1206 if (rv3d->persp == RV3D_CAMOB) {
1207 /* If autopersp and previous view was an axis one, switch back to PERSP mode, else reuse previous mode. */
1208 char persp = (autopersp && RV3D_VIEW_IS_AXIS(rv3d->lview)) ? RV3D_PERSP : rv3d->lpersp;
1209 view3d_persp_switch_from_camera(v3d, rv3d, persp);
1211 else if (autopersp && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1212 rv3d->persp = RV3D_PERSP;
1220 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1224 /* makes op->customdata */
1225 viewops_data_alloc(C, op);
1226 viewops_data_create(C, op, event);
1227 vod = op->customdata;
1229 /* poll should check but in some cases fails, see poll func for details */
1230 if (vod->rv3d->viewlock & RV3D_LOCKED) {
1231 viewops_data_free(C, op);
1232 return OPERATOR_PASS_THROUGH;
1235 /* switch from camera view when: */
1236 if (view3d_ensure_persp(vod->v3d, vod->ar)) {
1237 /* If we're switching from camera view to the perspective one,
1238 * need to tag viewport update, so camera vuew and borders
1239 * are properly updated.
1241 ED_region_tag_redraw(vod->ar);
1244 if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
1245 /* Rotate direction we keep always same */
1248 if (event->type == MOUSEPAN) {
1249 if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
1250 x = 2 * event->x - event->prevx;
1251 y = 2 * event->y - event->prevy;
1259 /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
1264 viewrotate_apply(vod, x, y);
1265 ED_view3d_depth_tag_update(vod->rv3d);
1267 viewops_data_free(C, op);
1269 return OPERATOR_FINISHED;
1272 /* add temp handler */
1273 WM_event_add_modal_handler(C, op);
1275 return OPERATOR_RUNNING_MODAL;
1279 /* test for unlocked camera view in quad view */
1280 static int view3d_camera_user_poll(bContext *C)
1285 if (ED_view3d_context_user_region(C, &v3d, &ar)) {
1286 RegionView3D *rv3d = ar->regiondata;
1287 if (rv3d->persp == RV3D_CAMOB) {
1295 static int view3d_lock_poll(bContext *C)
1297 View3D *v3d = CTX_wm_view3d(C);
1299 RegionView3D *rv3d = CTX_wm_region_view3d(C);
1301 return ED_view3d_offset_lock_check(v3d, rv3d);
1307 static void viewrotate_cancel(bContext *C, wmOperator *op)
1309 viewops_data_free(C, op);
1312 void VIEW3D_OT_rotate(wmOperatorType *ot)
1315 ot->name = "Rotate View";
1316 ot->description = "Rotate the view";
1317 ot->idname = "VIEW3D_OT_rotate";
1320 ot->invoke = viewrotate_invoke;
1321 ot->modal = viewrotate_modal;
1322 ot->poll = ED_operator_region_view3d_active;
1323 ot->cancel = viewrotate_cancel;
1326 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
1329 /** \name NDOF Utility Functions
1332 #define NDOF_HAS_TRANSLATE ((!ED_view3d_offset_lock_check(v3d, rv3d)) && !is_zero_v3(ndof->tvec))
1333 #define NDOF_HAS_ROTATE (((rv3d->viewlock & RV3D_LOCKED) == 0) && !is_zero_v3(ndof->rvec))
1336 * \param depth_pt: A point to calculate the depth (in perspective mode)
1338 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1340 float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1342 if (rv3d->is_persp) {
1343 speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1349 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1354 BLI_assert(dist >= 0.0f);
1356 copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1357 /* rv3d->viewinv isn't always valid */
1359 mul_mat3_m4_v3(rv3d->viewinv, tvec);
1361 invert_qt_qt(viewinv, rv3d->viewquat);
1362 mul_qt_v3(viewinv, tvec);
1365 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1368 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1371 negate_v3_v3(tvec, rv3d->ofs);
1373 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1377 * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1379 * \param has_zoom zoom, otherwise dolly, often `!rv3d->is_persp` since it doesnt make sense to dolly in ortho.
1381 static void view3d_ndof_pan_zoom(const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1382 const bool has_translate, const bool has_zoom)
1384 RegionView3D *rv3d = ar->regiondata;
1388 if (has_translate == false && has_zoom == false) {
1392 WM_event_ndof_pan_get(ndof, pan_vec, false);
1398 * velocity should be proportional to the linear velocity attained by rotational motion of same strength
1400 * proportional to arclength = radius * angle
1405 /* "zoom in" or "translate"? depends on zoom mode in user settings? */
1406 if (ndof->tvec[2]) {
1407 float zoom_distance = rv3d->dist * ndof->dt * ndof->tvec[2];
1409 if (U.ndof_flag & NDOF_ZOOM_INVERT)
1410 zoom_distance = -zoom_distance;
1412 rv3d->dist += zoom_distance;
1418 /* all callers must check */
1419 if (has_translate) {
1420 BLI_assert(ED_view3d_offset_lock_check((View3D *)sa->spacedata.first, rv3d) == false);
1424 if (has_translate) {
1425 const float speed = view3d_ndof_pan_speed_calc(rv3d);
1427 mul_v3_fl(pan_vec, speed * ndof->dt);
1429 /* transform motion from view to world coordinates */
1430 invert_qt_qt(view_inv, rv3d->viewquat);
1431 mul_qt_v3(view_inv, pan_vec);
1433 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1434 sub_v3_v3(rv3d->ofs, pan_vec);
1436 if (rv3d->viewlock & RV3D_BOXVIEW) {
1437 view3d_boxview_sync(sa, ar);
1443 static void view3d_ndof_orbit(const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1444 /* optional, can be NULL*/
1447 View3D *v3d = sa->spacedata.first;
1448 RegionView3D *rv3d = ar->regiondata;
1452 BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1454 view3d_ensure_persp(v3d, ar);
1456 rv3d->view = RV3D_VIEW_USER;
1458 invert_qt_qt(view_inv, rv3d->viewquat);
1460 if (U.ndof_flag & NDOF_TURNTABLE) {
1463 /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1464 float angle, quat[4];
1465 float xvec[3] = {1, 0, 0};
1467 /* only use XY, ignore Z */
1468 WM_event_ndof_rotate_get(ndof, rot);
1470 /* Determine the direction of the x vector (for rotating up and down) */
1471 mul_qt_v3(view_inv, xvec);
1473 /* Perform the up/down rotation */
1474 angle = ndof->dt * rot[0];
1475 quat[0] = cosf(angle);
1476 mul_v3_v3fl(quat + 1, xvec, sinf(angle));
1477 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1479 /* Perform the orbital rotation */
1480 angle = ndof->dt * rot[1];
1482 /* update the onscreen doo-dad */
1483 rv3d->rot_angle = angle;
1484 rv3d->rot_axis[0] = 0;
1485 rv3d->rot_axis[1] = 0;
1486 rv3d->rot_axis[2] = 1;
1488 quat[0] = cosf(angle);
1491 quat[3] = sinf(angle);
1492 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1498 float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1500 /* transform rotation axis from view to world coordinates */
1501 mul_qt_v3(view_inv, axis);
1503 /* update the onscreen doo-dad */
1504 rv3d->rot_angle = angle;
1505 copy_v3_v3(rv3d->rot_axis, axis);
1507 axis_angle_to_quat(quat, axis, angle);
1509 /* apply rotation */
1510 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1514 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1519 * Called from both fly mode and walk mode,
1521 void view3d_ndof_fly(
1522 const wmNDOFMotionData *ndof,
1523 View3D *v3d, RegionView3D *rv3d,
1524 const bool use_precision, const short protectflag,
1525 bool *r_has_translate, bool *r_has_rotate)
1527 bool has_translate = NDOF_HAS_TRANSLATE;
1528 bool has_rotate = NDOF_HAS_ROTATE;
1531 invert_qt_qt(view_inv, rv3d->viewquat);
1533 rv3d->rot_angle = 0.0f; /* disable onscreen rotation doo-dad */
1535 if (has_translate) {
1536 /* ignore real 'dist' since fly has its own speed settings,
1537 * also its overwritten at this point. */
1538 float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1539 float trans[3], trans_orig_y;
1544 WM_event_ndof_pan_get(ndof, trans, false);
1545 mul_v3_fl(trans, speed * ndof->dt);
1546 trans_orig_y = trans[1];
1548 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1552 /* transform motion from view to world coordinates */
1553 mul_qt_v3(view_inv, trans);
1555 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1556 /* replace world z component with device y (yes it makes sense) */
1557 trans[2] = trans_orig_y;
1560 if (rv3d->persp == RV3D_CAMOB) {
1561 /* respect camera position locks */
1562 if (protectflag & OB_LOCK_LOCX) trans[0] = 0.0f;
1563 if (protectflag & OB_LOCK_LOCY) trans[1] = 0.0f;
1564 if (protectflag & OB_LOCK_LOCZ) trans[2] = 0.0f;
1567 if (!is_zero_v3(trans)) {
1568 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1569 sub_v3_v3(rv3d->ofs, trans);
1570 has_translate = true;
1573 has_translate = false;
1578 const float turn_sensitivity = 1.0f;
1582 float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1584 if (fabsf(angle) > 0.0001f) {
1590 /* transform rotation axis from view to world coordinates */
1591 mul_qt_v3(view_inv, axis);
1593 /* apply rotation to view */
1594 axis_angle_to_quat(rotation, axis, angle);
1595 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1597 if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1598 /* force an upright viewpoint
1599 * TODO: make this less... sudden */
1600 float view_horizon[3] = {1.0f, 0.0f, 0.0f}; /* view +x */
1601 float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1603 /* find new inverse since viewquat has changed */
1604 invert_qt_qt(view_inv, rv3d->viewquat);
1605 /* could apply reverse rotation to existing view_inv to save a few cycles */
1607 /* transform view vectors to world coordinates */
1608 mul_qt_v3(view_inv, view_horizon);
1609 mul_qt_v3(view_inv, view_direction);
1612 /* find difference between view & world horizons
1613 * true horizon lives in world xy plane, so look only at difference in z */
1614 angle = -asinf(view_horizon[2]);
1616 /* rotate view so view horizon = world horizon */
1617 axis_angle_to_quat(rotation, view_direction, angle);
1618 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1621 rv3d->view = RV3D_VIEW_USER;
1628 *r_has_translate = has_translate;
1629 *r_has_rotate = has_rotate;
1635 /* -- "orbit" navigation (trackball/turntable)
1637 * -- panning in rotationally-locked views
1639 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1642 if (event->type != NDOF_MOTION) {
1643 return OPERATOR_CANCELLED;
1650 const wmNDOFMotionData *ndof = event->customdata;
1652 viewops_data_alloc(C, op);
1653 viewops_data_create_ex(C, op, event,
1654 (U.uiflag & USER_ORBIT_SELECTION) != 0, false);
1656 vod = op->customdata;
1660 /* off by default, until changed later this function */
1661 rv3d->rot_angle = 0.0f;
1663 ED_view3d_camera_lock_init_ex(v3d, rv3d, false);
1665 if (ndof->progress != P_FINISHING) {
1666 const bool has_rotation = NDOF_HAS_ROTATE;
1667 /* if we can't rotate, fallback to translate (locked axis views) */
1668 const bool has_translate = NDOF_HAS_TRANSLATE && (rv3d->viewlock & RV3D_LOCKED);
1669 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1671 if (has_translate || has_zoom) {
1672 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1676 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod);
1680 ED_view3d_camera_lock_sync(v3d, rv3d);
1682 ED_region_tag_redraw(vod->ar);
1684 viewops_data_free(C, op);
1686 return OPERATOR_FINISHED;
1690 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1693 ot->name = "NDOF Orbit View";
1694 ot->description = "Orbit the view using the 3D mouse";
1695 ot->idname = "VIEW3D_OT_ndof_orbit";
1698 ot->invoke = ndof_orbit_invoke;
1699 ot->poll = ED_operator_view3d_active;
1705 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1708 if (event->type != NDOF_MOTION) {
1709 return OPERATOR_CANCELLED;
1716 const wmNDOFMotionData *ndof = event->customdata;
1718 viewops_data_alloc(C, op);
1719 viewops_data_create_ex(C, op, event,
1720 (U.uiflag & USER_ORBIT_SELECTION) != 0, false);
1722 vod = op->customdata;
1726 /* off by default, until changed later this function */
1727 rv3d->rot_angle = 0.0f;
1729 ED_view3d_camera_lock_init_ex(v3d, rv3d, false);
1731 if (ndof->progress == P_FINISHING) {
1734 else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1735 /* if we can't rotate, fallback to translate (locked axis views) */
1736 const bool has_translate = NDOF_HAS_TRANSLATE;
1737 const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1739 if (has_translate || has_zoom) {
1740 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, true);
1743 else if ((U.ndof_flag & NDOF_MODE_ORBIT) ||
1744 ED_view3d_offset_lock_check(v3d, rv3d))
1746 const bool has_rotation = NDOF_HAS_ROTATE;
1747 const bool has_zoom = (ndof->tvec[2] != 0.0f);
1750 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, false, has_zoom);
1754 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod);
1757 else { /* free/explore (like fly mode) */
1758 const bool has_rotation = NDOF_HAS_ROTATE;
1759 const bool has_translate = NDOF_HAS_TRANSLATE;
1760 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1764 if (has_translate || has_zoom) {
1765 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1768 dist_backup = rv3d->dist;
1769 ED_view3d_distance_set(rv3d, 0.0f);
1772 view3d_ndof_orbit(ndof, vod->sa, vod->ar, NULL);
1775 ED_view3d_distance_set(rv3d, dist_backup);
1778 ED_view3d_camera_lock_sync(v3d, rv3d);
1780 ED_region_tag_redraw(vod->ar);
1782 viewops_data_free(C, op);
1784 return OPERATOR_FINISHED;
1788 void VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType *ot)
1791 ot->name = "NDOF Orbit View with Zoom";
1792 ot->description = "Orbit and zoom the view using the 3D mouse";
1793 ot->idname = "VIEW3D_OT_ndof_orbit_zoom";
1796 ot->invoke = ndof_orbit_zoom_invoke;
1797 ot->poll = ED_operator_view3d_active;
1803 /* -- "pan" navigation
1806 static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1808 if (event->type != NDOF_MOTION) {
1809 return OPERATOR_CANCELLED;
1812 View3D *v3d = CTX_wm_view3d(C);
1813 RegionView3D *rv3d = CTX_wm_region_view3d(C);
1814 const wmNDOFMotionData *ndof = event->customdata;
1816 const bool has_translate = NDOF_HAS_TRANSLATE;
1817 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1819 /* we're panning here! so erase any leftover rotation from other operators */
1820 rv3d->rot_angle = 0.0f;
1822 if (!(has_translate || has_zoom))
1823 return OPERATOR_CANCELLED;
1825 ED_view3d_camera_lock_init_ex(v3d, rv3d, false);
1827 if (ndof->progress != P_FINISHING) {
1828 ScrArea *sa = CTX_wm_area(C);
1829 ARegion *ar = CTX_wm_region(C);
1831 if (has_translate || has_zoom) {
1832 view3d_ndof_pan_zoom(ndof, sa, ar, has_translate, has_zoom);
1836 ED_view3d_camera_lock_sync(v3d, rv3d);
1838 ED_region_tag_redraw(CTX_wm_region(C));
1840 return OPERATOR_FINISHED;
1844 void VIEW3D_OT_ndof_pan(struct wmOperatorType *ot)
1847 ot->name = "NDOF Pan View";
1848 ot->description = "Pan the view with the 3D mouse";
1849 ot->idname = "VIEW3D_OT_ndof_pan";
1852 ot->invoke = ndof_pan_invoke;
1853 ot->poll = ED_operator_view3d_active;
1861 * wraps #ndof_orbit_zoom but never restrict to orbit.
1863 static int ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1865 /* weak!, but it works */
1866 const int ndof_flag = U.ndof_flag;
1869 U.ndof_flag &= ~NDOF_MODE_ORBIT;
1871 ret = ndof_orbit_zoom_invoke(C, op, event);
1873 U.ndof_flag = ndof_flag;
1878 void VIEW3D_OT_ndof_all(struct wmOperatorType *ot)
1881 ot->name = "NDOF Move View";
1882 ot->description = "Pan and rotate the view with the 3D mouse";
1883 ot->idname = "VIEW3D_OT_ndof_all";
1886 ot->invoke = ndof_all_invoke;
1887 ot->poll = ED_operator_view3d_active;
1893 /* ************************ viewmove ******************************** */
1896 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
1898 /* called in transform_ops.c, on each regeneration of keymaps */
1899 void viewmove_modal_keymap(wmKeyConfig *keyconf)
1901 static EnumPropertyItem modal_items[] = {
1902 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1904 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
1905 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1907 {0, NULL, 0, NULL, NULL}
1910 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Move Modal");
1912 /* this function is called for each spacetype, only needs to add map once */
1913 if (keymap && keymap->modal_items) return;
1915 keymap = WM_modalkeymap_add(keyconf, "View3D Move Modal", modal_items);
1917 /* items for modal map */
1918 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1919 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1921 /* disabled mode switching for now, can re-implement better, later on */
1923 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1924 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1925 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1928 /* assign map to operators */
1929 WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
1933 static void viewmove_apply(ViewOpsData *vod, int x, int y)
1935 if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) {
1936 vod->rv3d->ofs_lock[0] -= ((vod->oldx - x) * 2.0f) / (float)vod->ar->winx;
1937 vod->rv3d->ofs_lock[1] -= ((vod->oldy - y) * 2.0f) / (float)vod->ar->winy;
1939 else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) {
1940 const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1941 vod->rv3d->camdx += (vod->oldx - x) / (vod->ar->winx * zoomfac);
1942 vod->rv3d->camdy += (vod->oldy - y) / (vod->ar->winy * zoomfac);
1943 CLAMP(vod->rv3d->camdx, -1.0f, 1.0f);
1944 CLAMP(vod->rv3d->camdy, -1.0f, 1.0f);
1950 mval_f[0] = x - vod->oldx;
1951 mval_f[1] = y - vod->oldy;
1952 ED_view3d_win_to_delta(vod->ar, mval_f, dvec, vod->zfac);
1954 add_v3_v3(vod->rv3d->ofs, dvec);
1956 if (vod->rv3d->viewlock & RV3D_BOXVIEW)
1957 view3d_boxview_sync(vod->sa, vod->ar);
1963 ED_view3d_camera_lock_sync(vod->v3d, vod->rv3d);
1965 ED_region_tag_redraw(vod->ar);
1969 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
1972 ViewOpsData *vod = op->customdata;
1973 short event_code = VIEW_PASS;
1974 bool use_autokey = false;
1975 int ret = OPERATOR_RUNNING_MODAL;
1977 /* execute the events */
1978 if (event->type == MOUSEMOVE) {
1979 event_code = VIEW_APPLY;
1981 else if (event->type == EVT_MODAL_MAP) {
1982 switch (event->val) {
1983 case VIEW_MODAL_CONFIRM:
1984 event_code = VIEW_CONFIRM;
1986 case VIEWROT_MODAL_SWITCH_ZOOM:
1987 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1988 event_code = VIEW_CONFIRM;
1990 case VIEWROT_MODAL_SWITCH_ROTATE:
1991 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
1992 event_code = VIEW_CONFIRM;
1996 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
1997 event_code = VIEW_CONFIRM;
2000 if (event_code == VIEW_APPLY) {
2001 viewmove_apply(vod, event->x, event->y);
2002 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2006 else if (event_code == VIEW_CONFIRM) {
2007 ED_view3d_depth_tag_update(vod->rv3d);
2009 ret = OPERATOR_FINISHED;
2013 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2016 if (ret & OPERATOR_FINISHED) {
2017 viewops_data_free(C, op);
2023 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2027 /* makes op->customdata */
2028 viewops_data_alloc(C, op);
2029 viewops_data_create(C, op, event);
2030 vod = op->customdata;
2032 if (event->type == MOUSEPAN) {
2033 /* invert it, trackpad scroll follows same principle as 2d windows this way */
2034 viewmove_apply(vod, 2 * event->x - event->prevx, 2 * event->y - event->prevy);
2035 ED_view3d_depth_tag_update(vod->rv3d);
2037 viewops_data_free(C, op);
2039 return OPERATOR_FINISHED;
2042 /* add temp handler */
2043 WM_event_add_modal_handler(C, op);
2045 return OPERATOR_RUNNING_MODAL;
2049 static void viewmove_cancel(bContext *C, wmOperator *op)
2051 viewops_data_free(C, op);
2054 void VIEW3D_OT_move(wmOperatorType *ot)
2058 ot->name = "Move View";
2059 ot->description = "Move the view";
2060 ot->idname = "VIEW3D_OT_move";
2063 ot->invoke = viewmove_invoke;
2064 ot->modal = viewmove_modal;
2065 ot->poll = ED_operator_view3d_active;
2066 ot->cancel = viewmove_cancel;
2069 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2072 /* ************************ viewzoom ******************************** */
2074 /* viewdolly_modal_keymap has an exact copy of this, apply fixes to both */
2075 /* called in transform_ops.c, on each regeneration of keymaps */
2076 void viewzoom_modal_keymap(wmKeyConfig *keyconf)
2078 static EnumPropertyItem modal_items[] = {
2079 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2081 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2082 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2084 {0, NULL, 0, NULL, NULL}
2087 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Zoom Modal");
2089 /* this function is called for each spacetype, only needs to add map once */
2090 if (keymap && keymap->modal_items) return;
2092 keymap = WM_modalkeymap_add(keyconf, "View3D Zoom Modal", modal_items);
2094 /* items for modal map */
2095 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2096 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2098 /* disabled mode switching for now, can re-implement better, later on */
2100 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2101 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2102 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2105 /* assign map to operators */
2106 WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
2109 static void view_zoom_mouseloc_camera(
2110 Scene *scene, View3D *v3d,
2111 ARegion *ar, float dfac, int mx, int my)
2113 RegionView3D *rv3d = ar->regiondata;
2114 const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
2115 const float zoomfac_new = CLAMPIS(zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
2116 const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
2119 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
2121 rctf camera_frame_old;
2122 rctf camera_frame_new;
2124 const float pt_src[2] = {mx, my};
2128 ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, &camera_frame_old, false);
2129 BLI_rctf_translate(&camera_frame_old, ar->winrct.xmin, ar->winrct.ymin);
2131 rv3d->camzoom = camzoom_new;
2132 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
2134 ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, &camera_frame_new, false);
2135 BLI_rctf_translate(&camera_frame_new, ar->winrct.xmin, ar->winrct.ymin);
2137 BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
2138 sub_v2_v2v2(delta_px, pt_dst, pt_src);
2140 /* translate the camera offset using pixel space delta
2141 * mapped back to the camera (same logic as panning in camera view) */
2142 zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
2144 rv3d->camdx += delta_px[0] / (ar->winx * zoomfac_px);
2145 rv3d->camdy += delta_px[1] / (ar->winy * zoomfac_px);
2146 CLAMP(rv3d->camdx, -1.0f, 1.0f);
2147 CLAMP(rv3d->camdy, -1.0f, 1.0f);
2150 rv3d->camzoom = camzoom_new;
2151 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
2155 static void view_zoom_mouseloc_3d(ARegion *ar, float dfac, int mx, int my)
2157 RegionView3D *rv3d = ar->regiondata;
2158 const float dist_new = rv3d->dist * dfac;
2160 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
2168 negate_v3_v3(tpos, rv3d->ofs);
2170 mval_f[0] = (float)(((mx - ar->winrct.xmin) * 2) - ar->winx) / 2.0f;
2171 mval_f[1] = (float)(((my - ar->winrct.ymin) * 2) - ar->winy) / 2.0f;
2173 /* Project cursor position into 3D space */
2174 zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
2175 ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
2177 /* Calculate view target position for dolly */
2178 add_v3_v3v3(tvec, tpos, dvec);
2181 /* Offset to target position and dolly */
2182 copy_v3_v3(rv3d->ofs, tvec);
2183 rv3d->dist = dist_new;
2185 /* Calculate final offset */
2186 madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
2189 rv3d->dist = dist_new;
2193 static float viewzoom_scale_value(
2195 const short viewzoom,
2196 const bool zoom_invert, const bool zoom_invert_force,
2197 const int xy[2], const int xy_orig[2],
2198 const float val, const float val_orig,
2199 double *r_timer_lastdraw)
2203 if (viewzoom == USER_ZOOM_CONT) {
2204 double time = PIL_check_seconds_timer();
2205 float time_step = (float)(time - *r_timer_lastdraw);
2208 if (U.uiflag & USER_ZOOM_HORIZ) {
2209 fac = (float)(xy_orig[0] - xy[0]);
2212 fac = (float)(xy_orig[1] - xy[1]);
2215 if (zoom_invert != zoom_invert_force) {
2220 zfac = 1.0f + ((fac / 20.0f) * time_step);
2221 *r_timer_lastdraw = time;
2223 else if (viewzoom == USER_ZOOM_SCALE) {
2224 /* method which zooms based on how far you move the mouse */
2226 const int ctr[2] = {
2227 BLI_rcti_cent_x(winrct),
2228 BLI_rcti_cent_y(winrct),
2230 float len_new = 5 + len_v2v2_int(ctr, xy);
2231 float len_old = 5 + len_v2v2_int(ctr, xy_orig);
2233 /* intentionally ignore 'zoom_invert' for scale */
2234 if (zoom_invert_force) {
2235 SWAP(float, len_new, len_old);
2238 zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
2240 else { /* USER_ZOOM_DOLLY */
2244 if (U.uiflag & USER_ZOOM_HORIZ) {
2245 len_new += (winrct->xmax - xy[0]);
2246 len_old += (winrct->xmax - xy_orig[0]);
2249 len_new += (winrct->ymax - xy[1]);
2250 len_old += (winrct->ymax - xy_orig[1]);
2253 if (zoom_invert != zoom_invert_force) {
2254 SWAP(float, len_new, len_old);
2257 zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
2264 static void viewzoom_apply_camera(
2265 ViewOpsData *vod, const int xy[2],
2266 const short viewzoom, const bool zoom_invert)
2269 float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->camzoom_prev) * 2.0f;
2270 float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
2272 zfac = viewzoom_scale_value(
2273 &vod->ar->winrct, viewzoom, zoom_invert, true, xy, &vod->origx,
2274 zoomfac, zoomfac_prev,
2275 &vod->timer_lastdraw);
2277 if (zfac != 1.0f && zfac != 0.0f) {
2278 /* calculate inverted, then invert again (needed because of camera zoom scaling) */
2280 view_zoom_mouseloc_camera(
2281 vod->scene, vod->v3d,
2282 vod->ar, zfac, vod->oldx, vod->oldy);
2285 ED_region_tag_redraw(vod->ar);
2288 static void viewzoom_apply_3d(
2289 ViewOpsData *vod, const int xy[2],
2290 const short viewzoom, const bool zoom_invert)
2293 float dist_range[2];
2295 ED_view3d_dist_range_get(vod->v3d, dist_range);
2297 zfac = viewzoom_scale_value(
2298 &vod->ar->winrct, viewzoom, zoom_invert, false, xy, &vod->origx,
2299 vod->rv3d->dist, vod->dist_prev,
2300 &vod->timer_lastdraw);
2303 const float zfac_min = dist_range[0] / vod->rv3d->dist;
2304 const float zfac_max = dist_range[1] / vod->rv3d->dist;
2305 CLAMP(zfac, zfac_min, zfac_max);
2307 view_zoom_mouseloc_3d(
2308 vod->ar, zfac, vod->oldx, vod->oldy);
2311 /* these limits were in old code too */
2312 CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2314 if (vod->rv3d->viewlock & RV3D_BOXVIEW)
2315 view3d_boxview_sync(vod->sa, vod->ar);
2317 ED_view3d_camera_lock_sync(vod->v3d, vod->rv3d);
2319 ED_region_tag_redraw(vod->ar);
2322 static void viewzoom_apply(
2323 ViewOpsData *vod, const int xy[2],
2324 const short viewzoom, const bool zoom_invert)
2326 if ((vod->rv3d->persp == RV3D_CAMOB) &&
2327 (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0)
2329 viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert);
2332 viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert);
2336 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2338 ViewOpsData *vod = op->customdata;
2339 short event_code = VIEW_PASS;
2340 bool use_autokey = false;
2341 int ret = OPERATOR_RUNNING_MODAL;
2343 /* execute the events */
2344 if (event->type == TIMER && event->customdata == vod->timer) {
2345 /* continuous zoom */
2346 event_code = VIEW_APPLY;
2348 else if (event->type == MOUSEMOVE) {
2349 event_code = VIEW_APPLY;
2351 else if (event->type == EVT_MODAL_MAP) {
2352 switch (event->val) {
2353 case VIEW_MODAL_CONFIRM:
2354 event_code = VIEW_CONFIRM;
2356 case VIEWROT_MODAL_SWITCH_MOVE:
2357 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2358 event_code = VIEW_CONFIRM;
2360 case VIEWROT_MODAL_SWITCH_ROTATE:
2361 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2362 event_code = VIEW_CONFIRM;
2366 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
2367 event_code = VIEW_CONFIRM;
2370 if (event_code == VIEW_APPLY) {
2371 viewzoom_apply(vod, &event->x, U.viewzoom, (U.uiflag & USER_ZOOM_INVERT) != 0);
2372 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2376 else if (event_code == VIEW_CONFIRM) {
2377 ED_view3d_depth_tag_update(vod->rv3d);
2379 ret = OPERATOR_FINISHED;
2383 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2386 if (ret & OPERATOR_FINISHED) {
2387 viewops_data_free(C, op);
2393 static int viewzoom_exec(bContext *C, wmOperator *op)
2395 Scene *scene = CTX_data_scene(C);
2401 float dist_range[2];
2403 const int delta = RNA_int_get(op->ptr, "delta");
2406 if (op->customdata) {
2407 ViewOpsData *vod = op->customdata;
2413 sa = CTX_wm_area(C);
2414 ar = CTX_wm_region(C);
2417 v3d = sa->spacedata.first;
2418 rv3d = ar->regiondata;
2420 mx = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") : ar->winx / 2;
2421 my = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") : ar->winy / 2;
2423 use_cam_zoom = (rv3d->persp == RV3D_CAMOB) && !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2425 ED_view3d_dist_range_get(v3d, dist_range);
2428 const float step = 1.2f;
2429 /* this min and max is also in viewmove() */
2431 view_zoom_mouseloc_camera(scene, v3d, ar, step, mx, my);
2434 if (rv3d->dist < dist_range[1]) {
2435 view_zoom_mouseloc_3d(ar, step, mx, my);
2440 const float step = 1.0f / 1.2f;
2442 view_zoom_mouseloc_camera(scene, v3d, ar, step, mx, my);
2445 if (rv3d->dist > dist_range[0]) {
2446 view_zoom_mouseloc_3d(ar, step, mx, my);
2451 if (rv3d->viewlock & RV3D_BOXVIEW)
2452 view3d_boxview_sync(sa, ar);
2454 ED_view3d_depth_tag_update(rv3d);
2456 ED_view3d_camera_lock_sync(v3d, rv3d);
2457 ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2459 ED_region_tag_redraw(ar);
2461 viewops_data_free(C, op);
2463 return OPERATOR_FINISHED;
2466 /* this is an exact copy of viewzoom_modal_keymap */
2467 /* called in transform_ops.c, on each regeneration of keymaps */
2468 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2470 static EnumPropertyItem modal_items[] = {
2471 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2473 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2474 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2476 {0, NULL, 0, NULL, NULL}
2479 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Dolly Modal");
2481 /* this function is called for each spacetype, only needs to add map once */
2482 if (keymap && keymap->modal_items) return;
2484 keymap = WM_modalkeymap_add(keyconf, "View3D Dolly Modal", modal_items);
2486 /* items for modal map */
2487 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2488 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2490 /* disabled mode switching for now, can re-implement better, later on */
2492 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2493 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2494 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2497 /* assign map to operators */
2498 WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2501 /* viewdolly_invoke() copied this function, changes here may apply there */
2502 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2506 /* makes op->customdata */
2507 viewops_data_alloc(C, op);
2508 viewops_data_create(C, op, event);
2509 vod = op->customdata;
2511 /* if one or the other zoom position aren't set, set from event */
2512 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2513 RNA_int_set(op->ptr, "mx", event->x);
2514 RNA_int_set(op->ptr, "my", event->y);
2517 if (RNA_struct_property_is_set(op->ptr, "delta")) {
2518 viewzoom_exec(C, op);
2521 if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2523 if (U.uiflag & USER_ZOOM_HORIZ) {
2524 vod->origx = vod->oldx = event->x;
2525 viewzoom_apply(vod, &event->prevx, USER_ZOOM_DOLLY, (U.uiflag & USER_ZOOM_INVERT) != 0);
2528 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2529 vod->origy = vod->oldy = vod->origy + event->x - event->prevx;
2530 viewzoom_apply(vod, &event->prevx, USER_ZOOM_DOLLY, (U.uiflag & USER_ZOOM_INVERT) != 0);
2532 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2534 ED_view3d_depth_tag_update(vod->rv3d);
2536 viewops_data_free(C, op);
2537 return OPERATOR_FINISHED;
2540 if (U.viewzoom == USER_ZOOM_CONT) {
2541 /* needs a timer to continue redrawing */
2542 vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2543 vod->timer_lastdraw = PIL_check_seconds_timer();
2546 /* add temp handler */
2547 WM_event_add_modal_handler(C, op);
2549 return OPERATOR_RUNNING_MODAL;
2552 return OPERATOR_FINISHED;
2555 static void viewzoom_cancel(bContext *C, wmOperator *op)
2557 viewops_data_free(C, op);
2560 void VIEW3D_OT_zoom(wmOperatorType *ot)
2565 ot->name = "Zoom View";
2566 ot->description = "Zoom in/out in the view";
2567 ot->idname = "VIEW3D_OT_zoom";
2570 ot->invoke = viewzoom_invoke;
2571 ot->exec = viewzoom_exec;
2572 ot->modal = viewzoom_modal;
2573 ot->poll = ED_operator_region_view3d_active;
2574 ot->cancel = viewzoom_cancel;
2577 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2579 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2580 prop = RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Zoom Position X", "", 0, INT_MAX);
2581 RNA_def_property_flag(prop, PROP_HIDDEN);
2582 prop = RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Zoom Position Y", "", 0, INT_MAX);
2583 RNA_def_property_flag(prop, PROP_HIDDEN);
2587 /* ************************ viewdolly ******************************** */
2588 static void view_dolly_mouseloc(ARegion *ar, float orig_ofs[3], float dvec[3], float dfac)
2590 RegionView3D *rv3d = ar->regiondata;
2591 madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2594 static void viewdolly_apply(ViewOpsData *vod, int x, int y, const short zoom_invert)
2601 if (U.uiflag & USER_ZOOM_HORIZ) {
2602 len1 = (vod->ar->winrct.xmax - x) + 5;
2603 len2 = (vod->ar->winrct.xmax - vod->origx) + 5;
2606 len1 = (vod->ar->winrct.ymax - y) + 5;
2607 len2 = (vod->ar->winrct.ymax - vod->origy) + 5;
2610 SWAP(float, len1, len2);
2612 zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2616 view_dolly_mouseloc(vod->ar, vod->ofs, vod->mousevec, zfac);
2618 if (vod->rv3d->viewlock & RV3D_BOXVIEW)
2619 view3d_boxview_sync(vod->sa, vod->ar);
2621 ED_view3d_camera_lock_sync(vod->v3d, vod->rv3d);
2623 ED_region_tag_redraw(vod->ar);
2627 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2629 ViewOpsData *vod = op->customdata;
2630 short event_code = VIEW_PASS;
2631 bool use_autokey = false;
2632 int ret = OPERATOR_RUNNING_MODAL;
2634 /* execute the events */
2635 if (event->type == MOUSEMOVE) {
2636 event_code = VIEW_APPLY;
2638 else if (event->type == EVT_MODAL_MAP) {
2639 switch (event->val) {
2640 case VIEW_MODAL_CONFIRM:
2641 event_code = VIEW_CONFIRM;
2643 case VIEWROT_MODAL_SWITCH_MOVE:
2644 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2645 event_code = VIEW_CONFIRM;
2647 case VIEWROT_MODAL_SWITCH_ROTATE:
2648 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2649 event_code = VIEW_CONFIRM;
2653 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
2654 event_code = VIEW_CONFIRM;
2657 if (event_code == VIEW_APPLY) {
2658 viewdolly_apply(vod, event->x, event->y, (U.uiflag & USER_ZOOM_INVERT) != 0);
2659 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2663 else if (event_code == VIEW_CONFIRM) {
2664 ED_view3d_depth_tag_update(vod->rv3d);
2666 ret = OPERATOR_FINISHED;
2670 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2673 if (ret & OPERATOR_FINISHED) {
2674 viewops_data_free(C, op);
2680 static int viewdolly_exec(bContext *C, wmOperator *op)
2688 const int delta = RNA_int_get(op->ptr, "delta");
2690 if (op->customdata) {
2691 ViewOpsData *vod = op->customdata;
2695 copy_v3_v3(mousevec, vod->mousevec);
2698 sa = CTX_wm_area(C);
2699 ar = CTX_wm_region(C);
2700 negate_v3_v3(mousevec, ((RegionView3D *)ar->regiondata)->viewinv[2]);
2701 normalize_v3(mousevec);
2704 v3d = sa->spacedata.first;
2705 rv3d = ar->regiondata;
2707 /* overwrite the mouse vector with the view direction (zoom into the center) */
2708 if ((U.uiflag & USER_ZOOM_TO_MOUSEPOS) == 0) {
2709 normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2713 view_dolly_mouseloc(ar, rv3d->ofs, mousevec, 0.2f);
2716 view_dolly_mouseloc(ar, rv3d->ofs, mousevec, 1.8f);
2719 if (rv3d->viewlock & RV3D_BOXVIEW)
2720 view3d_boxview_sync(sa, ar);
2722 ED_view3d_depth_tag_update(rv3d);
2724 ED_view3d_camera_lock_sync(v3d, rv3d);
2726 ED_region_tag_redraw(ar);
2728 viewops_data_free(C, op);
2730 return OPERATOR_FINISHED;
2733 /* copied from viewzoom_invoke(), changes here may apply there */
2734 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2738 if (view3d_operator_offset_lock_check(C, op))
2739 return OPERATOR_CANCELLED;
2741 /* makes op->customdata */
2742 viewops_data_alloc(C, op);
2743 vod = op->customdata;
2745 /* poll should check but in some cases fails, see poll func for details */
2746 if (vod->rv3d->viewlock & RV3D_LOCKED) {
2747 viewops_data_free(C, op);
2748 return OPERATOR_PASS_THROUGH;
2751 /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2752 /* switch from camera view when: */
2753 if (vod->rv3d->persp != RV3D_PERSP) {
2754 if (vod->rv3d->persp == RV3D_CAMOB) {
2755 /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2756 view3d_persp_switch_from_camera(vod->v3d, vod->rv3d, RV3D_PERSP);
2759 vod->rv3d->persp = RV3D_PERSP;
2761 ED_region_tag_redraw(vod->ar);
2764 viewops_data_create(C, op, event);
2767 /* if one or the other zoom position aren't set, set from event */
2768 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2769 RNA_int_set(op->ptr, "mx", event->x);
2770 RNA_int_set(op->ptr, "my", event->y);
2773 if (RNA_struct_property_is_set(op->ptr, "delta")) {
2774 viewdolly_exec(C, op);
2777 /* overwrite the mouse vector with the view direction (zoom into the center) */
2778 if ((U.uiflag & USER_ZOOM_TO_MOUSEPOS) == 0) {
2779 negate_v3_v3(vod->mousevec, vod->rv3d->viewinv[2]);
2780 normalize_v3(vod->mousevec);
2783 if (event->type == MOUSEZOOM) {
2784 /* Bypass Zoom invert flag for track pads (pass false always) */
2786 if (U.uiflag & USER_ZOOM_HORIZ) {
2787 vod->origx = vod->oldx = event->x;
2788 viewdolly_apply(vod, event->prevx, event->prevy, (U.uiflag & USER_ZOOM_INVERT) == 0);
2792 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2793 vod->origy = vod->oldy = vod->origy + event->x - event->prevx;
2794 viewdolly_apply(vod, event->prevx, event->prevy, (U.uiflag & USER_ZOOM_INVERT) == 0);
2796 ED_view3d_depth_tag_update(vod->rv3d);
2798 viewops_data_free(C, op);
2799 return OPERATOR_FINISHED;
2802 /* add temp handler */
2803 WM_event_add_modal_handler(C, op);
2805 return OPERATOR_RUNNING_MODAL;
2808 return OPERATOR_FINISHED;
2811 static void viewdolly_cancel(bContext *C, wmOperator *op)
2813 viewops_data_free(C, op);
2816 void VIEW3D_OT_dolly(wmOperatorType *ot)
2819 ot->name = "Dolly View";
2820 ot->description = "Dolly in/out in the view";
2821 ot->idname = "VIEW3D_OT_dolly";
2824 ot->invoke = viewdolly_invoke;
2825 ot->exec = viewdolly_exec;
2826 ot->modal = viewdolly_modal;
2827 ot->poll = ED_operator_region_view3d_active;
2828 ot->cancel = viewdolly_cancel;
2831 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2833 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2834 RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Zoom Position X", "", 0, INT_MAX);
2835 RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Zoom Position Y", "", 0, INT_MAX);
2838 static void view3d_from_minmax(bContext *C, View3D *v3d, ARegion *ar,
2839 const float min[3], const float max[3],
2840 bool ok_dist, const int smooth_viewtx)
2842 RegionView3D *rv3d = ar->regiondata;
2850 sub_v3_v3v3(afm, max, min);
2851 size = max_fff(afm[0], afm[1], afm[2]);
2856 if (rv3d->is_persp) {
2857 if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2865 if (size < 0.0001f) {
2866 /* bounding box was a single point so do not zoom */
2870 /* adjust zoom so it looks nicer */
2876 new_dist = ED_view3d_radius_to_dist(v3d, ar, persp, true, (size / 2) * VIEW3D_MARGIN);
2877 if (rv3d->is_persp) {
2878 /* don't zoom closer than the near clipping plane */
2879 new_dist = max_ff(new_dist, v3d->near * 1.5f);
2884 mid_v3_v3v3(new_ofs, min, max);
2887 if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2888 rv3d->persp = RV3D_PERSP;
2889 ED_view3d_smooth_view(C, v3d, ar, v3d->camera, NULL,
2890 new_ofs, NULL, ok_dist ? &new_dist : NULL, NULL,
2894 ED_view3d_smooth_view(C, v3d, ar, NULL, NULL,
2895 new_ofs, NULL, ok_dist ? &new_dist : NULL, NULL,
2899 /* smooth view does viewlock RV3D_BOXVIEW copy */
2902 /* same as view3d_from_minmax but for all regions (except cameras) */
2903 static void view3d_from_minmax_multi(bContext *C, View3D *v3d,
2904 const float min[3], const float max[3],
2905 const bool ok_dist, const int smooth_viewtx)
2907 ScrArea *sa = CTX_wm_area(C);
2909 for (ar = sa->regionbase.first; ar; ar = ar->next) {
2910 if (ar->regiontype == RGN_TYPE_WINDOW) {
2911 RegionView3D *rv3d = ar->regiondata;
2912 /* when using all regions, don't jump out of camera view,
2913 * but _do_ allow locked cameras to be moved */
2914 if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2915 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2921 static int view3d_all_exec(bContext *C, wmOperator *op) /* was view3d_home() in 2.4x */
2923 ARegion *ar = CTX_wm_region(C);
2924 View3D *v3d = CTX_wm_view3d(C);
2925 Scene *scene = CTX_data_scene(C);
2928 const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2929 const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2930 /* any one of the regions may be locked */
2931 (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2932 const bool center = RNA_boolean_get(op->ptr, "center");
2933 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2935 float min[3], max[3];
2936 bool changed = false;
2939 /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2940 curs = ED_view3d_cursor3d_get(scene, v3d);
2946 INIT_MINMAX(min, max);
2949 for (base = scene->base.first; base; base = base->next) {
2950 if (BASE_VISIBLE(v3d, base)) {
2953 if (skip_camera && base->object == v3d->camera) {
2957 BKE_object_minmax(base->object, min, max, false);
2961 ED_region_tag_redraw(ar);
2962 /* TODO - should this be cancel?
2963 * I think no, because we always move the cursor, with or without
2964 * object, but in this case there is no change in the scene,
2965 * only the cursor so I choice a ED_region_tag like
2966 * view3d_smooth_view do for the center_cursor.
2969 return OPERATOR_FINISHED;
2972 if (use_all_regions) {
2973 view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
2976 view3d_from_minmax(C, v3d, ar, min, max, true, smooth_viewtx);
2979 return OPERATOR_FINISHED;
2983 void VIEW3D_OT_view_all(wmOperatorType *ot)
2988 ot->name = "View All";
2989 ot->description = "View all objects in scene";
2990 ot->idname = "VIEW3D_OT_view_all";
2993 ot->exec = view3d_all_exec;
2994 ot->poll = ED_operator_region_view3d_active;
2999 prop = RNA_def_boolean(ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
3000 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3001 RNA_def_boolean(ot->srna, "center", 0, "Center", "");
3004 /* like a localview without local!, was centerview() in 2.4x */
3005 static int viewselected_exec(bContext *C, wmOperator *op)
3007 ARegion *ar = CTX_wm_region(C);
3008 View3D *v3d = CTX_wm_view3d(C);
3009 Scene *scene = CTX_data_scene(C);
3011 Object *obedit = CTX_data_edit_object(C);
3012 float min[3], max[3];
3013 bool ok = false, ok_dist = true;
3014 const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
3015 const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
3016 /* any one of the regions may be locked */
3017 (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
3018 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3020 INIT_MINMAX(min, max);
3022 if (ob && (ob->mode & OB_MODE_WEIGHT_PAINT)) {
3023 /* hard-coded exception, we look for the one selected armature */
3024 /* this is weak code this way, we should make a generic active/selection callback interface once... */
3026 for (base = scene->base.first; base; base = base->next) {
3027 if (TESTBASELIB(v3d, base)) {
3028 if (base->object->type == OB_ARMATURE)
3029 if (base->object->mode & OB_MODE_POSE)
3039 ok = ED_view3d_minmax_verts(obedit, min, max); /* only selected */
3041 else if (ob && (ob->mode & OB_MODE_POSE)) {
3043 bArmature *arm = ob->data;
3044 bPoseChannel *pchan;
3047 for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
3048 if (pchan->bone->flag & BONE_SELECTED) {
3049 if (pchan->bone->layer & arm->layer) {
3050 bPoseChannel *pchan_tx = pchan->custom_tx ? pchan->custom_tx : pchan;
3052 mul_v3_m4v3(vec, ob->obmat, pchan_tx->pose_head);
3053 minmax_v3v3_v3(min, max, vec);
3054 mul_v3_m4v3(vec, ob->obmat, pchan_tx->pose_tail);
3055 minmax_v3v3_v3(min, max, vec);
3061 else if (BKE_paint_select_face_test(ob)) {
3062 ok = paintface_minmax(ob, min, max);
3064 else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) {
3065 ok = PE_minmax(scene, min, max);
3067 else if (ob && (ob->mode & (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT))) {
3068 BKE_paint_stroke_get_average(scene, ob, min);
3069 copy_v3_v3(max, min);
3071 ok_dist = 0; /* don't zoom */
3075 for (base = FIRSTBASE; base; base = base->next) {
3076 if (TESTBASE(v3d, base)) {
3078 if (skip_camera && base->object == v3d->camera) {
3082 /* account for duplis */
3083 if (BKE_object_minmax_dupli(scene, base->object, min, max, false) == 0)
3084 BKE_object_minmax(base->object, min, max, false); /* use if duplis not found */
3092 return OPERATOR_FINISHED;
3095 if (use_all_regions) {
3096 view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
3099 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
3102 // XXX BIF_view3d_previewrender_signal(curarea, PR_DBASE|PR_DISPRECT);
3104 return OPERATOR_FINISHED;
3107 void VIEW3D_OT_view_selected(wmOperatorType *ot)
3112 ot->name = "View Selected";
3113 ot->description = "Move the view to the selection center";
3114 ot->idname = "VIEW3D_OT_view_selected";
3117 ot->exec = viewselected_exec;
3118 ot->poll = ED_operator_region_view3d_active;
3124 prop = RNA_def_boolean(ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
3125 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3128 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
3130 View3D *v3d = CTX_wm_view3d(C);
3133 ED_view3D_lock_clear(v3d);
3135 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3137 return OPERATOR_FINISHED;
3140 return OPERATOR_CANCELLED;
3144 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
3148 ot->name = "View Lock Clear";
3149 ot->description = "Clear all view locking";
3150 ot->idname = "VIEW3D_OT_view_lock_clear";
3153 ot->exec = view_lock_clear_exec;
3154 ot->poll = ED_operator_region_view3d_active;
3160 static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op))
3162 View3D *v3d = CTX_wm_view3d(C);
3163 Object *obact = CTX_data_active_object(C);
3167 ED_view3D_lock_clear(v3d);
3169 v3d->ob_centre = obact; /* can be NULL */
3171 if (obact && obact->type == OB_ARMATURE) {
3172 if (obact->mode & OB_MODE_POSE) {
3173 bPoseChannel *pcham_act = BKE_pose_channel_active(obact);
3175 BLI_strncpy(v3d->ob_centre_bone, pcham_act->name, sizeof(v3d->ob_centre_bone));
3179 EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone;
3181 BLI_strncpy(v3d->ob_centre_bone, ebone_act->name, sizeof(v3d->ob_centre_bone));
3186 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3188 return OPERATOR_FINISHED;
3191 return OPERATOR_CANCELLED;