8
$\begingroup$

I want to use a panel to expose the Zoom ratio that is currently being used in the 3D viewport. I can see that there are bpy.ops functions that can be used to set this ratio, but I do not want to modify it, I simply want to get the current ratio. This is from a post on this site for an older version of Blender:

    zoom = context.space_data.zoom[0]*100
    zoom_h = context.space_data.zoom[1]*100
    if math.isclose(zoom,zoom_h,rel_tol=0.001):
        self.layout.label(text="{:.1f}%".format(zoom))
    else:
        self.layout.label(text="Width:  {:.1f}%".format(zoom))
        self.layout.label(text="Height: {:.1f}%".format(zoom_h))

I have tried using this format, 'context.space_data.zoom[]' but that doesn't seem to be the way that the zoom data is stored in Blender 2.93.6 any information on the proper way to access this data would very helpful!

$\endgroup$
0

1 Answer 1

11
+250
$\begingroup$

Answering the actual question

It seems the closest thing to what you want is region.view_distance

Here's how to display it and other relevant informations about the current 3D viewport to the user :

import bpy
from math import pi


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "camera zoom"

    def draw(self, context):
        layout = self.layout
        [a.tag_redraw() for a in context.screen.areas]  # If we don't do this, values don't update unless you hover your mouse over the panel
        space = context.area.spaces.active
        region = space.region_3d
        col = layout.column()
        col.prop(space, "lens")
        # This is some kind of "zoom" but it doesn't go beyond 0 (eg. You can't zoom past region.view_location) :
        col.prop(region, "view_distance")
        col.prop(region, "view_location")
        col.prop(region, "view_rotation")
        euler = region.view_rotation.to_euler()
        # Seeing Euler values is far more convenient to the layman. So we convert the quaternion to euler angles :
        box = col.box()
        box.label(text="View Rotation")
        for axis in "x", "y", "z":
            angle = round(getattr(euler, axis) * 180 / pi, 2)
            box.label(text=f"{axis.upper()} : {angle}°")
        col.prop(region, "view_matrix")
        col.prop(region, "window_matrix")
        col.prop(region, "perspective_matrix")  # Per the docs this is readonly and = to window_matrix * view_matrix
        col.enabled=False # Comment this if you want to be able to tweak the fields
        

def register():
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

enter image description here

Answering the question I originally thought this was

That is to say, how to tweak the camera zoom when in camera view, with an actual camera object.


As a Script

import bpy

foo = 5
screen = bpy.context.screen
area = next(area for area in screen.areas if area.type == "VIEW_3D")
space = area.spaces.active
region_3d = space.region_3d
region_3d.view_camera_zoom += foo

enter image description here


In a Panel, in the properties editor

import bpy


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    def draw(self, context):
        layout = self.layout
        screen = context.screen
        area = next(area for area in screen.areas if area.type == "VIEW_3D")
        space = area.spaces.active
        region_3d = space.region_3d
        layout.prop(region_3d, "view_camera_zoom")


def register():
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

enter image description here


In a Panel, in the viewport N-panel

import bpy


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "camera zoom"

    def draw(self, context):
        layout = self.layout
        layout.prop(context.area.spaces.active.region_3d, "view_camera_zoom")


def register():
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

enter image description here


Let's find out how to get to this property

Typing "Camera Zoom" in the docs search bar yields a few results, among which we get a view_camera_zoom attribute that's a member of the RegionView3D class.

region_3d.view_camera_zoom += foo

On the very bottom of this class' page, among the References, we get a few links :

bpy.types.RegionView3D

enter image description here

Context.region_data : A property you can get with bpy.context.region_data. Unfortunately this will only be available if the script is run from an operator executing in the 3D viewport or if the operator has been overriden with an override dictionary which leads to an existing 3D viewport.

Spaceview3D.region_3d and SpaceView3D.region_quadviews : Both are properties available on the SpaceView3D class. The quad views property is actually a collection of 3D viewport regions (usually 4 of them) and can be used to retrieve the perspective view in the quad view (out of scope of this answer).

region_3d = space.region_3d

SpaceView3D is a children class of Space. Again, going to the bottom of the docs page you get :

bpy.types.Spaceview3D

enter image description here

So we can, once again, access the space data from the current context, but it has to correspond to a 3D viewport context so you have to go through hoops to get it unless your operator runs from the N-Panel for instance, in which case it does have a direct access to the space.

However we see that we have two other ways to get to it, and they both are through the Area object.

We arrive to a point were we do have to make a bit of a judgment call and where it's not really explicitely documented.

A View 3D area, as all other areas in Blender, has a spaces attribute. Luckily, this one only has one. So you are 100% sure area.spaces[0] and area.spaces.active are the exact same object.

space = area.spaces.active  # = area.spaces[0] too

Now how do you get the right area ? Once again, look at the bottom :

bpy.types.Area

enter image description here

And once again, we can get it from the context, but we won't try to because it's not straightfoward. Or we can get it from a Screen object.

area = next(area for area in screen.areas if area.type == "VIEW_3D")

Ditto, scroll to the bottom :

bpy.types.Screen

enter image description here

So now we do want to access the screen from Context because it is accessible from pretty much any context. If you're using multiple screens you might have to use another solution though.

Finally :

import bpy

screen = bpy.context.screen
$\endgroup$
6
  • $\begingroup$ Thank you very much for such a detailed and well documented answer! I have used the code provided in the viewport N-panel and that works as you describe. However, I am actually looking for the zoom value of the viewport itself, not the Camera object. I would like to display the zoom value, without allowing the user to modify, the Perspective, X, Y, and Z views within the 3D Viewport. Something akin to the current zoom value seen in the bottom right of a blank Microsoft Word Document, but displayed for each of the views when they are active within the 3D Viewport. $\endgroup$
    – TristanL
    Jan 20 at 17:51
  • $\begingroup$ Well yeah I figured after reading your question again that I may have misunderstood ! :) there isn't actually a zoom value in the 3D viewport regular view, but a matrix describing the position and rotation of the virtual camera, and an associated focal length. When you scroll with your mouse wheel, you're not zooming, you're moving the virtual camera backwards on its local Z-axis. You can get the view matrix with docs.blender.org/api/current/… $\endgroup$
    – Gorgious
    Jan 20 at 18:13
  • $\begingroup$ @TristanL just updated the start of my answer, this should answer you original question :) $\endgroup$
    – Gorgious
    Jan 20 at 19:08
  • 1
    $\begingroup$ + My first comment is wrong, the view distance property actually acts as a zoom buffer, so zooming isn't "just" moving a camera along it's Z axis. I guess it's because the "physical" zoom amount depends on how much you've already zoomed, and how far the object under the mouse is if any. FWIW hitting numpad "." kind of resets this zoom buffer. $\endgroup$
    – Gorgious
    Jan 20 at 19:17
  • $\begingroup$ Wow thank you very much for completely displaying the region_3d data, I had been looking at the distance while you wrote up the example you reposted above, and I think that will work for my goal. I want to change the grid_scale based on some range within the distance that is returned, now I need to capture the scroll up/down events in a modal and act on the region.view_distance. Side note, in your comment about the values needing the a.tag_redraw, I am only seeing the panel redraw if I select something, not if I move my viewport in/out/around. Is there a different option for this? $\endgroup$
    – TristanL
    Jan 20 at 21:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.