Page MenuHome

View3D: 2D viewport navigation mode
Changes PlannedPublic

Authored by Pablo Dobarro (pablodp606) on Aug 1 2019, 12:26 AM.
"Dislike" token, awarded by capnm."Mountain of Wealth" token, awarded by EitanSomething.



This patch introduces a new navigation mode that is specifically designed for pen tablets, with sculpting/texture painting workflows in mind. Rotation is slower, panning speed takes the working area into account and the zoom speed is always predictable.

The main purpose of the 2D navigation mode is working with objects of a limited size, so it is not that convenient for navigating large scenes. When used in areas of Blender like sculpt mode, texture paint or grease pencil, it adds an extra level of polish to the user experience.

For comparison, this is the standard navigation using a pen tablet. Orbit around selection and trackball rotation are enabled.

After enabling 2D viewport navigation, the 3D viewport becomes much more smooth and controllable. I'm not pressing any shortcuts to reset the view or to set the pivot point manually.

Diff Detail

rB Blender
view3d-2d-navigation (branched from master)
Build Status
Buildable 4270
Build 4270: arc lint + arc unit

Event Timeline

This looks pretty great from the video.

Would it make sense to automatically enable this behavior for tablet input, while keeping the existing behavior for mouse input? There could be an enum to choose between Auto / Mouse Optimized / Tablet Optimized in the preferences, rather than a 2D Navigation boolean.

I haven't tested this patch yet though. But I'm wondering if the existing navigation actually has many advantages for large scenes when using a tablet. At least for panning, having it match the actual movement seems like the right default behavior regardless of the task.

@Brecht Van Lommel (brecht) I've been using this panning code for almost a year and I didn't find any major problem, so maybe we can make it the default behavior.
The only inconvenience I found is that if your currently selected object is behind the camera the panning speed won't match. I'm not sure if that is a common situation in other workflows (also, the current implementation never matches the speed). For everything brush related, your last stroke is always contained in the screen, so it always works.

We should try to find a good default for the rotation speed factor, but probably this will always depend on your personal preference and your tablet size/screen size ratio. I made this patch with a factor of 0.7 because probably most people are using a smaller device. In my 16-inch tablet, I'm using 0.6.

Zooming is the trickiest part. This patch moves the ofs directly for zooming. This means that if you have selected object and you center the view to it, you can zoom through it. Also, big zoom changes are slower. On the other hand, it also means that your zoom speed is always predictable because it is using your current working area to calculate the zoom speed instead of the distance to ofs. The zoom implementation is more recent, so I only tested it with the pen tabled and a regular mouse wheel. We should probably test if this also works with multitouch devices and other input configurations.

This seems quite nice for tablet input.

Largely agree with @Brecht Van Lommel (brecht) that it could be nice to auto-detect tablets and then enable this, as many users don't think to manually change preferences when switching input devices. Auto / Mouse Optimized / Tablet Optimized seem like sensible options.

We might need the same kind of thing for the arrow cursor, which makes sense to hide for direct tablet input.

@William Reynish (billreynish) I'm testing this with automatic navigation mode switching when using a tablet/mouse, I think we can do the following:

  • Always keep the new panning code active. The difference is so big that most users are going to think that it is a bug.
  • Switch the rotation speed factor depending on the device, maybe we should make it an option with different mouse and tablet values
  • Switch the zooming to constant (the one implemented in this patch) when using a tabled. Keep the old code for other devices

That seems probably ok to me, although changing the way panning works generally is a very core change, so it just has to be done very carefully, and might require more testers.

Campbell Barton (campbellbarton) requested changes to this revision.EditedAug 2 2019, 1:54 AM
Panning Depth

In the demo video, the main thing this patch seems to solve is panning using the brushes last stroke location.

This is something auto-depth does although it uses the depth under the cursor instead of the selection/brush-stroke center (could be tweaked),
I'd like to know why we wouldn't use the existing method of defining a different depth for panning, instead of adding new panning logic.

We could for for eg, use the brushes last-stroke depth in paint modes (when auto-depth is enabled or as a separate option, can figure out the details later).

Using Selection Center

While this is nice when it's working, it relies on the calculated center point being in-front of the user in a predictable location. This isn't guaranteed in cases the user navigates to a new location, or is editing large objects such as buildings which might have their center behind the view.

Checked this patch and there is currently a bug when the center is behind the view - the entire view jumps to a new location when panning. This could be detected, using a fallback such as the depth under the cursor - or not using the preference at all, however the issue remains that navigating to a new view-point might give very slow panning or behave in a way that seems buggy.

Rotation Sensitivity

Not sure slowing rotation should be coupled with this functionality.

Current rotation speed seems quite fast, we could have a rotation-sensitivity preference (using this speed as a default) then people can speed it up if they prefer.

Initial Code Review
  • Uses magic numbers, it's not clear what they represent of if they're related to each-other.
  • Calling view3d_orbit_calc_center every mouse-motion isn't efficient since it may be looping over all vertices for eg to calculate the center.
  • view3d_orbit_calc_center is used for all modes, not just brush workflows. We could have a separate function that only uses the last brush stroke location if this is intended to be used in paint modes.

This can be a large number of the center point is far outside the view (even when it's close measured by depth).

I couldn't tell if this is intended, if not - both points can be projected onto the view plane before measuring.

This revision now requires changes to proceed.Aug 2 2019, 1:54 AM

I think logic could be moved into the initialization step, scaling vod->init.zfac.
Then this code could be left as-is.

@Campbell Barton (campbellbarton) Using auto-depth works in sculpt/paint modes, but it fails in grease pencil. We can use the stroke center for grease pencil and maybe add an option to switch between stroke center/cursor depth for the rest painting modes. Ideally, I think the best solution could be having a "click on mesh" event in paint modes. That event should not start a stroke and add an undo step, but it should update the rotation center and panning depth manually. It will also help to filter some random clicks that may happen in these modes.

I also agree with adding rotation sensibility as a separate option. I'll update the patch to include that option for turntable rotation as well while trying to match the speed with trackball rotation.

  • Add separate property for rotation sensitivity
Pablo Dobarro (pablodp606) planned changes to this revision.Aug 3 2019, 1:31 AM

Made rotation use angle units and committed rotation speed preference separately, rBe82b7f15270eb4e4dbcd119ea09d1f0204f1dc11

Made an alternative patch that gives similar functionality. Internally this adjusts the view center and distance after each stroke.

The main advantage is we avoid unexpected behavior when the user strokes then navigates to a different view position making the the stroke center very close or behind the view.

However this impacts view orbit and zoom too.

1diff --git a/release/scripts/startup/bl_ui/ b/release/scripts/startup/bl_ui/
2index 48aaf55bdfa..ee58036e06f 100644
3--- a/release/scripts/startup/bl_ui/
4+++ b/release/scripts/startup/bl_ui/
5@@ -1466,6 +1466,7 @@ class USERPREF_PT_navigation_orbit(PreferencePanel, Panel):
6 flow.prop(inputs, "use_rotate_around_active")
7 flow.prop(inputs, "use_auto_perspective")
8 flow.prop(inputs, "use_mouse_depth_navigate")
9+ flow.prop(inputs, "use_paint_depth_navigate")
10 if sys.platform == "darwin":
11 flow.prop(inputs, "use_trackpad_natural", text="Natural Trackpad Direction")
13diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c
14index e987a623d0b..9bee0ee4047 100644
15--- a/source/blender/blenloader/intern/versioning_userdef.c
16+++ b/source/blender/blenloader/intern/versioning_userdef.c
17@@ -523,7 +523,8 @@ void BLO_version_defaults_userpref_blend(Main *bmain, UserDef *userdef)
19 userdef->flag &= ~(USER_FLAG_UNUSED_4);
24 }
26 if (!USER_VERSION_ATLEAST(280, 41)) {
27diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
28index 6144f5751f2..34a087ab76e 100644
29--- a/source/blender/editors/sculpt_paint/paint_stroke.c
30+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
31@@ -842,6 +842,21 @@ static void stroke_done(struct bContext *C, struct wmOperator *op)
32 BLI_freelistN(&stroke->line);
34 paint_stroke_data_free(op);
36+ if (U.uiflag & USER_DEPTH_NAVIGATE_PAINT) {
37+ if (ups->last_stroke_valid && ups->average_stroke_counter > 0) {
38+ View3D *v3d = CTX_wm_view3d(C);
39+ RegionView3D *rv3d = CTX_wm_region_view3d(C);
40+ if (v3d && rv3d) {
41+ if (!(rv3d->persp != RV3D_PERSP || v3d->ob_centre_cursor || v3d->ob_centre)) {
42+ float dist_co[3];
43+ float fac = 1.0f / ups->average_stroke_counter;
44+ mul_v3_v3fl(dist_co, ups->average_stroke_accum, fac);
45+ ED_view3d_distance_set_from_location(rv3d, dist_co, v3d->clip_start);
46+ }
47+ }
48+ }
49+ }
50 }
52 /* Returns zero if the stroke dots should not be spaced, non-zero otherwise */
53diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
54index b8914c7a74f..f9f56df66a2 100644
55--- a/source/blender/makesdna/DNA_userdef_types.h
56+++ b/source/blender/makesdna/DNA_userdef_types.h
57@@ -928,7 +928,7 @@ typedef enum eUserpref_UI_Flag {
58 USER_MENUOPENAUTO = (1 << 9),
59 USER_DEPTH_CURSOR = (1 << 10),
60 USER_AUTOPERSP = (1 << 11),
61- USER_UIFLAG_UNUSED_12 = (1 << 12), /* cleared */
63 USER_GLOBALUNDO = (1 << 13),
64 USER_ORBIT_SELECTION = (1 << 14),
65 USER_DEPTH_NAVIGATE = (1 << 15),
66diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
67index 38cb3e1d222..170b624682b 100644
68--- a/source/blender/makesrna/intern/rna_userdef.c
69+++ b/source/blender/makesrna/intern/rna_userdef.c
70@@ -5162,6 +5162,11 @@ static void rna_def_userdef_input(BlenderRNA *brna)
71 "Auto Depth",
72 "Use the depth under the mouse to improve view pan/rotate/zoom functionality");
74+ prop = RNA_def_property(srna, "use_paint_depth_navigate", PROP_BOOLEAN, PROP_NONE);
75+ RNA_def_property_boolean_sdna(prop, NULL, "uiflag", USER_DEPTH_NAVIGATE_PAINT);
76+ RNA_def_property_ui_text(
77+ prop, "Auto Depth Paint", "Use the depth of paint brush strokes when painting");
79 prop = RNA_def_property(srna, "use_camera_lock_parent", PROP_BOOLEAN, PROP_NONE);
80 RNA_def_property_boolean_negative_sdna(prop, NULL, "uiflag", USER_CAM_LOCK_NO_PARENT);
81 RNA_def_property_ui_text(prop,

@Campbell Barton (campbellbarton) I've been testing your patch for a while with different models and I think that it is the kind of behavior we want for paint modes.
I think we should add a sensitivity factor for zooming as well. Right now if you adjust the rotation speed and you are using auto depth, the default zoom speed starts to feel a little bit faster compared to the rest of the movements. Something like:

float sensitivity = 0.55f;
zfac = val_orig * (sensitivity * 2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;