Blender V5.0
view3d_navigate_view_dolly.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_context.hh"
10#include "BKE_report.hh"
11
12#include "BLI_math_vector.h"
13
14#include "WM_api.hh"
15
16#include "RNA_access.hh"
17
18#include "ED_screen.hh"
19
20#include "view3d_intern.hh"
21#include "view3d_navigate.hh" /* own include */
22
23/* -------------------------------------------------------------------- */
29
31{
32 /* NOTE: This is an exact copy of #viewzoom_modal_keymap. */
33
34 static const EnumPropertyItem modal_items[] = {
35 {VIEW_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
36 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
37
38 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
39 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
40
41 {0, nullptr, 0, nullptr, nullptr},
42 };
43
44 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Dolly Modal");
45
46 /* This function is called for each space-type, only needs to add map once. */
47 if (keymap && keymap->modal_items) {
48 return;
49 }
50
51 keymap = WM_modalkeymap_ensure(keyconf, "View3D Dolly Modal", modal_items);
52
53 /* assign map to operators */
54 WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
55}
56
58{
59 View3D *v3d = CTX_wm_view3d(C);
61 if (ED_view3d_offset_lock_check(v3d, rv3d)) {
62 BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
63 return true;
64 }
65 return false;
66}
67
68static void view_dolly_to_vector_3d(ARegion *region,
69 const float orig_ofs[3],
70 const float dvec[3],
71 float dfac)
72{
73 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
74 madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
75}
76
77static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const bool zoom_invert)
78{
79 float zfac = 1.0;
80
81 {
82 float len1, len2;
83
84 if (U.uiflag & USER_ZOOM_HORIZ) {
85 len1 = (vod->region->winrct.xmax - xy[0]) + 5;
86 len2 = (vod->region->winrct.xmax - vod->init.event_xy[0]) + 5;
87 }
88 else {
89 len1 = (vod->region->winrct.ymax - xy[1]) + 5;
90 len2 = (vod->region->winrct.ymax - vod->init.event_xy[1]) + 5;
91 }
92 if (zoom_invert) {
93 std::swap(len1, len2);
94 }
95
96 zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
97 }
98
99 if (zfac != 1.0f) {
100 view_dolly_to_vector_3d(vod->region, vod->init.ofs, vod->init.mousevec, zfac);
101 }
102
103 if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
104 view3d_boxview_sync(vod->area, vod->region);
105 }
106
108
110}
111
113{
114 ViewOpsData *vod = static_cast<ViewOpsData *>(op->customdata);
115 short event_code = VIEW_PASS;
116 bool use_autokey = false;
118
119 /* Execute the events. */
120 if (event->type == EVT_MODAL_MAP) {
121 switch (event->val) {
123 event_code = VIEW_CONFIRM;
124 break;
127 C, "VIEW3D_OT_move", blender::wm::OpCallContext::InvokeDefault, nullptr, event);
128 event_code = VIEW_CONFIRM;
129 break;
132 C, "VIEW3D_OT_rotate", blender::wm::OpCallContext::InvokeDefault, nullptr, event);
133 event_code = VIEW_CONFIRM;
134 break;
135 }
136 }
137 else {
138 if (event->type == MOUSEMOVE) {
139 event_code = VIEW_APPLY;
140 }
141 else if (event->type == vod->init.event_type) {
142 if (event->val == KM_RELEASE) {
143 event_code = VIEW_CONFIRM;
144 }
145 }
146 else if (event->type == EVT_ESCKEY) {
147 if (event->val == KM_PRESS) {
148 event_code = VIEW_CANCEL;
149 }
150 }
151 }
152
153 switch (event_code) {
154 case VIEW_APPLY: {
155 viewdolly_apply(vod, event->xy, (U.uiflag & USER_ZOOM_INVERT) != 0);
157 use_autokey = true;
158 }
159 break;
160 }
161 case VIEW_CONFIRM: {
162 use_autokey = true;
164 break;
165 }
166 case VIEW_CANCEL: {
167 vod->state_restore();
169 break;
170 }
171 }
172
173 if (use_autokey) {
174 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
175 }
176
177 if ((ret & OPERATOR_RUNNING_MODAL) == 0) {
178 if (ret & OPERATOR_FINISHED) {
180 }
181 viewops_data_free(C, vod);
182 op->customdata = nullptr;
183 }
184
185 return ret;
186}
187
189{
190 View3D *v3d;
191 RegionView3D *rv3d;
192 ScrArea *area;
193 ARegion *region;
194 float mousevec[3];
195
196 const int delta = RNA_int_get(op->ptr, "delta");
197
198 if (op->customdata) {
199 ViewOpsData *vod = static_cast<ViewOpsData *>(op->customdata);
200
201 area = vod->area;
202 region = vod->region;
203 copy_v3_v3(mousevec, vod->init.mousevec);
204 }
205 else {
206 area = CTX_wm_area(C);
207 region = CTX_wm_region(C);
208 negate_v3_v3(mousevec, static_cast<RegionView3D *>(region->regiondata)->viewinv[2]);
209 normalize_v3(mousevec);
210 }
211
212 v3d = static_cast<View3D *>(area->spacedata.first);
213 rv3d = static_cast<RegionView3D *>(region->regiondata);
214
215 const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
216
217 /* overwrite the mouse vector with the view direction (zoom into the center) */
218 if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
219 normalize_v3_v3(mousevec, rv3d->viewinv[2]);
220 negate_v3(mousevec);
221 }
222
223 view_dolly_to_vector_3d(region, rv3d->ofs, mousevec, delta < 0 ? 1.8f : 0.2f);
224
225 if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
226 view3d_boxview_sync(area, region);
227 }
228
230
231 ED_region_tag_redraw(region);
232
233 viewops_data_free(C, static_cast<ViewOpsData *>(op->customdata));
234 op->customdata = nullptr;
235
236 return OPERATOR_FINISHED;
237}
238
240{
241 /* Near duplicate logic in #viewzoom_invoke(), changes here may apply there too. */
242
243 ViewOpsData *vod;
244
246 return OPERATOR_CANCELLED;
247 }
248
249 const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
250
251 vod = viewops_data_create(C, event, &ViewOpsType_dolly, use_cursor_init);
252 op->customdata = vod;
253
255
256 /* Rationale for enforcing a perspective projection:
257 *
258 * While translating the view center (the #RegionView3D::ofs) is possible,
259 * in most cases there is no user feedback that anything is changing,
260 * because only "panning" the view is shown in orthographic projections.
261 *
262 * From a user perspective it seems like a bug when interactive operators appear to do nothing,
263 * so force a perspective view.
264 *
265 * There are some exceptions where users would notice (mentioning for completeness),
266 * but they're obscure enough for the logic to stay as-is.
267 *
268 * - With a small far-clip plane "dolly" may move contents in/out of the visible clipping range.
269 * - With quad-view and "Sync Zoom/Pan" enabled, "dolly" will be visible other views.
270 * We could even make an exception for this and allow dolly however even in this case
271 * the user might as well pan the other views directly.
272 *
273 * NOTE: needs to run before #viewops_data_create so the backup `rv3d->ofs` is correct.
274 */
275 if (vod->rv3d->persp != RV3D_PERSP) {
276 if (vod->rv3d->persp == RV3D_CAMOB) {
277 /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
280 }
281 else {
282 vod->rv3d->persp = RV3D_PERSP;
283 }
285 }
286
287 /* if one or the other zoom position aren't set, set from event */
288 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
289 RNA_int_set(op->ptr, "mx", event->xy[0]);
290 RNA_int_set(op->ptr, "my", event->xy[1]);
291 }
292
293 if (RNA_struct_property_is_set(op->ptr, "delta")) {
294 viewdolly_exec(C, op);
295 }
296 else {
297 /* overwrite the mouse vector with the view direction (zoom into the center) */
298 if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
299 negate_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
301 }
302
303 if (event->type == MOUSEZOOM) {
304 /* Bypass Zoom invert flag for track pads (pass false always) */
305
306 if (U.uiflag & USER_ZOOM_HORIZ) {
307 vod->init.event_xy[0] = vod->prev.event_xy[0] = event->xy[0];
308 }
309 else {
310 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
311 vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->xy[0] -
312 event->prev_xy[0];
313 }
314 viewdolly_apply(vod, event->prev_xy, (U.uiflag & USER_ZOOM_INVERT) == 0);
315
316 viewops_data_free(C, static_cast<ViewOpsData *>(op->customdata));
317 op->customdata = nullptr;
318 return OPERATOR_FINISHED;
319 }
320
321 /* add temp handler */
324 }
325 return OPERATOR_FINISHED;
326}
327
329{
330 /* identifiers */
331 ot->name = "Dolly View";
332 ot->description = "Dolly in/out in the view";
333 ot->idname = ViewOpsType_dolly.idname;
334
335 /* API callbacks. */
336 ot->invoke = viewdolly_invoke;
337 ot->exec = viewdolly_exec;
338 ot->modal = viewdolly_modal;
339 /* Check rotation because this operator switches from orthographic to perspective view.
340 * See inline code-comments for details. */
343
344 /* flags */
346
347 /* properties */
350}
351
353
356 /*idname*/ "VIEW3D_OT_dolly",
357 /*poll_fn*/ nullptr,
358 /*init_fn*/ nullptr,
359 /*apply_fn*/ nullptr,
360};
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
RegionView3D * CTX_wm_region_view3d(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
View3D * CTX_wm_view3d(const bContext *C)
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void negate_v3_v3(float r[3], const float a[3])
MINLINE void negate_v3(float r[3])
MINLINE float normalize_v3_v3(float r[3], const float a[3])
MINLINE void madd_v3_v3v3fl(float r[3], const float a[3], const float b[3], float f)
MINLINE float normalize_v3(float n[3])
@ USER_ZOOM_INVERT
@ USER_ZOOM_TO_MOUSEPOS
@ USER_ZOOM_HORIZ
#define RV3D_LOCK_FLAGS(rv3d)
@ RV3D_CAMOB
@ RV3D_PERSP
@ RV3D_BOXVIEW
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
bScreen * ED_screen_animation_playing(const wmWindowManager *wm)
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
bool ED_view3d_camera_lock_sync(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d)
bool ED_view3d_offset_lock_check(const View3D *v3d, const RegionView3D *rv3d)
void ED_view3d_persp_switch_from_camera(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d, char persp)
bool ED_view3d_camera_lock_undo_push(const char *str, const View3D *v3d, const RegionView3D *rv3d, bContext *C)
bool ED_view3d_camera_lock_autokey(View3D *v3d, RegionView3D *rv3d, bContext *C, bool do_rotate, bool do_translate)
#define C
Definition RandGen.cpp:29
@ KM_PRESS
Definition WM_types.hh:311
@ KM_RELEASE
Definition WM_types.hh:312
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
@ OPTYPE_DEPENDS_ON_CURSOR
Definition WM_types.hh:218
@ OPTYPE_GRAB_CURSOR_XY
Definition WM_types.hh:188
#define U
BPy_StructRNA * depsgraph
@ VIEW_CONFIRM
Definition image_ops.cc:601
@ VIEW_PASS
Definition image_ops.cc:599
@ VIEW_APPLY
Definition image_ops.cc:600
return ret
void RNA_int_set(PointerRNA *ptr, const char *name, int value)
int RNA_int_get(PointerRNA *ptr, const char *name)
bool RNA_struct_property_is_set(PointerRNA *ptr, const char *identifier)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
void * regiondata
void * first
float viewinv[4][4]
ListBase spacedata
Depsgraph * depsgraph
blender::int2 event_xy
struct ViewOpsData::@244345216304223004112237167211144027240265370072 prev
RegionView3D * rv3d
struct ViewOpsData::@010113264246165170144271002355152266226235365232 init
VecBase< T, 2 > xy() const
int ymax
int xmax
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int xy[2]
Definition WM_types.hh:761
int prev_xy[2]
Definition WM_types.hh:820
const void * modal_items
const char * name
Definition WM_types.hh:1033
struct ReportList * reports
struct wmOperatorType * type
struct PointerRNA * ptr
void view3d_boxview_sync(ScrArea *area, ARegion *region)
void view3d_navigate_cancel_fn(bContext *C, wmOperator *op)
void view3d_operator_properties_common(wmOperatorType *ot, const enum eV3D_OpPropFlag flag)
void viewops_data_free(bContext *C, ViewOpsData *vod)
ViewOpsData * viewops_data_create(bContext *C, const wmEvent *event, const ViewOpsType *nav_type, const bool use_cursor_init)
bool view3d_zoom_or_dolly_or_rotation_poll(bContext *C)
@ VIEWOPS_FLAG_DEPTH_NAVIGATE
@ VIEWOPS_FLAG_ZOOM_TO_MOUSE
@ VIEW_CANCEL
ViewOpsType ViewOpsType_dolly
@ V3D_OP_PROP_USE_MOUSE_INIT
@ V3D_OP_PROP_DELTA
@ V3D_OP_PROP_MOUSE_CO
void ED_view3d_smooth_view_force_finish(bContext *C, View3D *v3d, ARegion *region)
@ VIEWROT_MODAL_SWITCH_ROTATE
@ VIEW_MODAL_CANCEL
@ VIEWROT_MODAL_SWITCH_MOVE
@ VIEW_MODAL_CONFIRM
static wmOperatorStatus viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
static void view_dolly_to_vector_3d(ARegion *region, const float orig_ofs[3], const float dvec[3], float dfac)
static wmOperatorStatus viewdolly_exec(bContext *C, wmOperator *op)
static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const bool zoom_invert)
static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
static wmOperatorStatus viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
void viewdolly_modal_keymap(wmKeyConfig *keyconf)
void VIEW3D_OT_dolly(wmOperatorType *ot)
int xy[2]
Definition wm_draw.cc:178
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
@ EVT_MODAL_MAP
@ MOUSEZOOM
@ MOUSEMOVE
@ EVT_ESCKEY
wmOperatorType * ot
Definition wm_files.cc:4237
wmKeyMap * WM_modalkeymap_ensure(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items)
Definition wm_keymap.cc:932
void WM_modalkeymap_assign(wmKeyMap *km, const char *opname)
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:959