2
$\begingroup$

So I have a simple UI like this:

import bpy
from bpy.props import *
from bpy.app.handlers import persistent

bpy.types.Scene.intNumber = IntProperty(name="intNumber", description="Enter int number between 1 and 100", default=1, min=1, max=100)
bpy.types.Scene.previousNumber = IntProperty(name="prevNumber", description="the previous value", default=1, min=1, max=100)

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 = "object"

    def draw(self, context):
        layout = self.layout

        obj = context.object

        row = layout.row()
        row.prop(context.scene, "intNumber")

@persistent
def scene_update(scene):
    if bpy.context.scene.intNumber != bpy.types.Scene.previousNumber :
        print(bpy.context.scene.intNumber)
        bpy.types.Scene.previousNumber = bpy.context.scene.intNumber 

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.app.handlers.scene_update_post.append(scene_update)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    bpy.app.handlers.scene_update_post.remove(scene_update)


if __name__ == "__main__":
    register()

This creates a UI element, a number dial to be specific, on the object panel. When I'm using the UI element, so clicking and dragging, it will print the number to the console. But the problem is it also prints numbers while I'm dialing.

Output of this when dragging the UI element from 1 to 40:

1
6
14
17
20
25
29
31
34
36
40

I want to get the numbers like this:

1
40

I only want to get the number where the user stops dragging / dialing, or releases the mouse button. Of course it should work in both directions (dragging up / down)

Update: Although CoDEmanX approaches are flawless and working, it's still not exactly what I was searching for. I realized that this "stop value" is actually there, in the console there appears a

ED_undo_push: intNumber: 40

once I let go of the UI element. Could another solution lie in getting hold of that value?

$\endgroup$
  • $\begingroup$ No, I'd really need this. $\endgroup$ – bortran Sep 11 '15 at 14:37
  • $\begingroup$ I'd suggest using a modal timer operator rather than a scene_update handler $\endgroup$ – batFINGER Sep 11 '15 at 17:31
3
$\begingroup$

There is no sophisticated event system that would let you distinguish between onChange and afterChange events. Changes to properties will immediately be evaluated. It allows for instant feedback, which is usually desired.

However, it's a nightmare in terms of user experience if the triggered operation takes very long. It will block Blender until completion, then start the next eval cycle if you keep dragging, resulting in a very unresponsive application.

In such a situation, in which you do not want immediate evaluation, use a props dialog (as opposed to a props popup, which comes with instant eval):

import bpy

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    myProp = bpy.props.FloatProperty(
        name="My Property",
        min=0,
        max=10,
        default=5,
        subtype='FACTOR'
    )

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def execute(self, context):
        self.report({'ERROR'}, "myProp = {}".format(self.myProp))
        return {'FINISHED'}


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.object.simple_operator('INVOKE_DEFAULT')

Note that it needs to be invoked to show the dialog. If you call the operator from Python console for instance, then it will be executed and not show a dialog.

Operators added to the interface are invoked by default:

bpy.types.RENDER_PT_render.append(
    lambda self, context: self.layout.operator("object.simple_operator")
)

Another approach is to have an additional property (e.g. on the WindowManager type) and add that to a panel. Then let the user adjust the value and click a button to assign the value to the actual property. A callback on the actual property will only be called when the property is assigned by the operator (or manually by the user in the Python console for instance), but not as the property in the panel is dragged - because that's a different property:

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 = "object"

    def draw(self, context):
        layout = self.layout
        wm = context.window_manager
        layout.prop(wm, "myProp")
        props = layout.operator("object.assign_value")
        props.helperProp = wm.myProp


class ObjectAssignValue(bpy.types.Operator):
    bl_label = "Assign Value"
    bl_idname = "object.assign_value"

    helperProp = bpy.props.FloatProperty()

    @classmethod
    def poll(cls, context):
        return context.object is not None

    def execute(self, context):
        ob = context.object
        ob.myProp = self.helperProp
        self.report({'INFO'}, "Assigned: {}".format(self.helperProp))
        return {'FINISHED'}


def register():
    bpy.types.Object.myProp = bpy.props.FloatProperty(update=lambda self, context: print(self.myProp))
    bpy.types.WindowManager.myProp = bpy.props.FloatProperty()
    bpy.utils.register_class(HelloWorldPanel)
    bpy.utils.register_class(ObjectAssignValue)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    bpy.utils.unregister_class(ObjectAssignValue)
    del bpy.types.Object.myProp
    del bpy.types.WindowManager.myProp


if __name__ == "__main__":
    register()
$\endgroup$

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.