I am trying to build a workflow for painting equirectangular images. (I would pirate photoshop as I know it can do this but I am averse to running proprietary software)

As seen in the image below I have a basic setup that doesn't require any addons. I use a camera, the camera is locked to the world origin. I have to use two viewports both in camera view, one that has the camera locked to the viewport and one where I can zoom in and out of the frame with my drawing pads equivalent of the scroll wheel.

The advice I've gotten from someone on blenderartists has essentially been "this is the best possible solution and you should just get used to it". But this workflow diverges from my muscle memory of 2d image editing software in far too many places to be usable for me.

I need someone familiar enough with the blender api to tell me if its possible to create an addon that can temporarily override blender's 3d viewport controls so that dragging with the mmb pivots the camera around as if you are grabbing on to a point of the sphere and rotating it (like google street view), and so that scrolling changes the focal length of the viewport instead of moving the viewport closer to or further from the orbit point while still being able to use the viewport for texture painting. (I know that the blender api allows one to modify the viewport's matrix—and I presume the viewport focal length is also modifiable—so I guess its more that I'm asking if there's a way to temporarily suspend and override certain input mappings. That or a new creative solution I haven't considered yet.)

I imagine that this would probably be more easily solved in blender's source code, but if there's a way to solve this puzzle without setting up a build environment for blender, or complaining to a dev and waiting for the next major release I'd find that preferable (im not one to run from the testing repos).

Edit. Im also trying to see if I can explain what I need to chat gpt, if I find the information I need before any of you I'll post the solution myself.

it can be done its just annoying

  • $\begingroup$ It's also possible to just lock camera location values and it will just rotate while you orbit as usual so if you use a camera for navigation and lock viewport to it, it will act as you want while orbiting, but unfortunately there is no native modal operator for changing focal length interactively so you still need to script that. $\endgroup$ Sep 25, 2023 at 13:25
  • $\begingroup$ @MartynasŽiemys that was the first thing I tried. The issue was that I'm trying to make it feel as natural as working in 2d painting software, and the panning is inverted from the natural 2d panning experience when all you do is lock a camera with a locked position to the viewport. My script attempts to take care of that. And since I don't have to lock the camera to the viewport the ability to zoom in and out of the camera frame is preserved so changing the actual focal length of the camera is actually unnecessary. $\endgroup$ Sep 27, 2023 at 2:23

1 Answer 1


I’ve derived a rudimentary way of acheiving the kind of controls i need, thank—in small part—to chat gpt and stack exchange. It turned out that using a modal operator was all it took to override the standard blender controls. Though I couldn’t find a way to easily manipulate the viewport rotation and focal length. instead I create a temporary camera and set it as the default scene camera and automatically switch the first viewport in the current area to camera view. Though you can use it in any area by switching to camera view in that area. I need to make it so that the mmb is only overridden when the mouse is in a View3d region, and the rotation speed of the camera should be slower when the viewport is zoomed in and faster when its zoomed out, but this is a pretty decent proof of concept for now. Right now the script must be run manually and pressing ESC deletes the temporary camera and restores the default controls.

import bpy
from mathutils import *

class CustomViewportControls(bpy.types.Operator):
    """overrides blenders standard viewport controls"""
    bl_idname = "wm.custom_viewport_controls"
    bl_label = "Custom Viewport Controls"
    mmb_pressed = False
    cam = None
    cam_obj = None
    cam_mat = Matrix.Rotation(3.1415926/2,3,'X')
    ini_mpos = Vector((0.0,0.0))
    rot_state= Vector((0.0,0.0))

    def init_custom_navigation(self):
        if not "Pivot_cam" in bpy.data.cameras:
        if not "Pivot_cam" in bpy.data.objects:  
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                # Get the region of the 3D Viewport
    def destroy_custom_navigation(self):
        if "Pivot_cam" in bpy.data.objects:
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                # Get the region of the 3D Viewport
    def modal(self, context, event):
        mpos=Vector((event.mouse_x, event.mouse_y))
        if event.type in {'ESC'}:
            return {'CANCELLED'}
        if event.type == 'MOUSEMOVE' and self.mmb_pressed:
            ## code for panning the viewport
            #print("mouse moved", mdist)
            self.cam_obj.rotation_euler=(cam_mat @ angle_x @ angle_y).to_euler()
            self.ini_mpos = mpos
            return {'RUNNING_MODAL'}
        if event.type in {'MIDDLEMOUSE'}and not event.ctrl:
            #print("mmb", event.value)
            if event.value == "PRESS":
                self.mmb_pressed = True
                self.ini_mpos= mpos
                self.mmb_pressed = False
            return {'RUNNING_MODAL'}

        if event.type in {'ESC'}:
            return {'FINISHED'}
        return {'PASS_THROUGH'}
    def invoke(self, context, event):
        return {'RUNNING_MODAL'}

    def execute(self, context):
        wm = context.window_manager
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        return {'FINISHED'}

def register():

def unregister():

if __name__ == "__main__":
  • 1
    $\begingroup$ It would be better to move the answer part from the question and edit it in here to the answer. It is totally fine to answer your own questions when you find a solution because it's very helpful for people facing similar issues who might search for a solution in the future. $\endgroup$ Sep 25, 2023 at 13:29
  • $\begingroup$ yeah, I only realized I provided an answer to the question as I worded it here after the fact. Was too lazy to move things around until now. $\endgroup$ Sep 27, 2023 at 2:31
  • $\begingroup$ oof I've begun to run into the consequences of running a modal operator continuously, (and with the wrong context), I feel like it would be best if I could temporarily override the view3d.rotate keybindings that use the middle mouse button. But I'm not getting any indications that such a feat is possible. $\endgroup$ Sep 28, 2023 at 3:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .