11

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!

TristanL
  • 113
  • 6

1 Answers1

16

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, "is_perspective")
    col.prop(region, "view_perspective")
    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

Gorgious
  • 30,723
  • 2
  • 44
  • 101
  • 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. – TristanL Jan 20 '22 at 17:51
  • 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 https://docs.blender.org/api/current/bpy.types.RegionView3D.html#bpy.types.RegionView3D.view_matrix – Gorgious Jan 20 '22 at 18:13
  • @TristanL just updated the start of my answer, this should answer you original question :) – Gorgious Jan 20 '22 at 19:08
  • 1
  • 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.
  • – Gorgious Jan 20 '22 at 19:17
  • 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? – TristanL Jan 20 '22 at 21:34
  • @TristanL happy to help ! :) Well after testing this again it seems this line actually does nothing, as you said it's the fact that an object is selected that triggers the panel redraw. Don't really know how to override it, I'll snoop around :) – Gorgious Jan 21 '22 at 06:26