How can a slider increment vary with viewport zoom?

I’m writing an add-on that uses sliders in the tool panel to control the values of variables that in turn control the positions of objects in the scene.

Generally, each variable has a specific ‘step’ value that is added or subtracted as the slider is dragged to the left or right:

bpy.types.Scene.x_pos = bpy.props.FloatProperty(update = move_object, name = “X Position”, step = .1)

The problem is, if I zoom out, I want that step value to be larger, so I can move the object farther with a smaller mouse drag. While if I zoom very far in, I want that step value to be very small, so I can move the object with a fine degree of control.

Is there a way to do this?

Just a very old bit of code I wrote that gets the viewport location, which is really all you need to know.

import bpy
from bpy.types import Operator
from bpy_extras.view3d_utils import region_2d_to_origin_3d


class OP_align_ui( Operator ):
    bl_idname = "dbo.align_ui"
    bl_label = "UI Align" 


    def modal(self, context, event):
        if event.type == 'ESC':
            print('MODAL FINISHED')
            return {'FINISHED'}
        print(context.region_data.view_location)

        #context.region_data.view_location
        print(context.region_data.view_matrix)


        # All this to get the viewport location?
        viewport_loc = region_2d_to_origin_3d(context.region, context.space_data.region_3d, event.mouse_region_x, event.mouse_region_y)
        bpy.data.objects["Empty"].location = viewport_loc
        print(viewport_loc)


        #context.scene.camera.matrix_world = context.region_data.view_matrix
        return {'PASS_THROUGH'}


    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)


register()

3dbi, thanks for the code! Could you elaborate a little? How could I use the viewport location to alter the step value of a given variable?

The problem is that when I’m zoomed out, having a very tiny step value is inconvenient because the target object moves incredibly slowly across the screen. If I use a larger step value in the code, then the object will move faster across the screen when I drag its slider. But the problem then becomes what happens when I’m zoomed in all the way. At that point, with a super large step value, the object will zip right past me, and I will have no fine-grained control over its position.

Oh hell I think I might have misunderstood you! I just assumed when you said ‘zooming’ you actually meant translating the viewport camera around in 3d space, but now I think you may be taking about changing it’s focal length i.e. it’s lens property. If that’s the case I don’t really know the answer other that to map that one range of values to another (see below) but if on the other hand you do just mean translating the viewport camera around then it’s just basic vector maths.

All you need to do is subtract one vector location from the other and the resulting vector’s magnitude/length will equal the distance between the two objects. If you know the distance from the camera to the object you can then decide what range of distances to map to your step values and then just clamp any distance that exceed this range.

for example:

step_min = 1
step_max = 100
dist_min = 2
dist_max = 60

dist = max(dist_min, min(VECTOR_RESULT.length, dist_max))

slope = (step_max - step_min) / (dist_max - dist_min)
step_value = step_min + slope * (dist - dist_min)

I’m sure there’s probably a nicer way to do it in python but that’s the actual maths.

It’s possible that all I need is the value of the “viewport lens angle”. But when I hover over the ‘lens’ slider in the ‘View’ tab of the N-Key panel, blender pops up a helper window with incomplete information.

It says:
“Python: SpaceView3D.lens
bpy.data.screens[“Default”] … lens”

That information doesn’t lead anywhere. If I go to the python console, I get this:

>>> SpaceView3D.lens
Traceback (most recent call last):
File “<blender_console>”, line 1, in <module>
NameError: name ‘SpaceView3D’ is not defined

If I go back to the View tab and rightclick on the ‘lens’ slider, the “Copy Data Path” option is greyed out so I can’t copy the data path.

If I do a google search, I find this API page: https://pythonapi.upbge.org/bpy.types.SpaceView3D.html#bpy.types.SpaceView3D.lens

Right there it says that ‘lens’ is the value I want, but it doesn’t give me any way to access it. At the top of the page it says:

“bpy.types.SpaceView3D(Space)”

So I click on the link to “Space” and it suggests using a value of “VIEW_3D”. Looks right to me, since that’s where I am in blender when I want to use it.

I go back to the python console in blender and try it:

>>> bpy.types.SpaceView3D(VIEW_3D).lens
Traceback (most recent call last):
File “<blender_console>”, line 1, in <module>
NameError: name ‘VIEW_3D’ is not defined

So I’m left with the same frustrating question. After all my research, I still don’t know how to access the lens variable of the viewport.

‘’’
bpy.types contains a list of class types not actually instances of those classes for example :

>>> type(bpy.context.scene)

will print:

<class ‘bpy.types.Scene’>

So bpy.context.scene holds a reference to an instance of the Scene class. In fact it holds a reference to the currently selected/active scene.

If we wanted to get a reference to another scene we would have to use

bpy.data.scenes

Something you may want to try:

In the python console type:

>>> bpy.

and press ctrl+spacebar then type

>>> bpy.data.scree

and press ctrl+spacebar then press ctrl+spacebar again and so on…

You may also want to try, if you haven’t already, Visual Studio Code with Python extension or Eclipse with PyDev. Either one of these free IDE’s will allow you to set breakpoints in your Python scripts then enter debug mode, then when you execute your script in Blender, and the program hits a breakpoint, all the properties and their values within the scope of your executing code will be visible to you.

Although personal I usually just end up using Sublime Text most of the time along with the Blender Python console.

import bpy

class TestExample(bpy.types.Operator):

    bl_idname = "view3d.test_example"
    bl_label = "Test Example"


    @classmethod 
    def poll(cls, context):
        return context.space_data.type == 'VIEW_3D'


    def execute(self, context):
        space_data = context.space_data

        print(dir(space_data))
        print(type(space_data))
        print('Lens: %s' % space_data.lens)

        return{'FINISHED'}


class TestExample2(bpy.types.Operator):

    bl_idname = "view3d.test_example2"
    bl_label = "Test Example 2"

    def execute(self, context):
        screen = context.screen
        areas = screen.areas

        #find all view 3d spaces in the current active screen
        spaces_3d = [area.spaces[0] for area in areas if area.type == 'VIEW_3D']

        print("
==== %i View 3D Space(s) Found =====" % len(spaces_3d))
        for s in spaces_3d:
            print("%s.lens = %f
" % (s, s.lens))

        #Get the active view 3d space if there is one
        active_3d_space = context.area.spaces[0] if context.area.type == 'VIEW_3D' else None
        print("active_3d_space: %s" % active_3d_space)

        return{'FINISHED'}


def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()