2
$\begingroup$

Suppose I have a Python function get_loc() that returns a Vector, which I want to use to drive the location of an object interactively (e.g., triggered by me moving another object). I can set up a scripted expression on each axis, e.g. "get_loc()[0]" for X, "get_loc()[1]" for Y, "get_loc()[2]" for Z. But I assume this is calling get_loc() three times.

Is there a way (maybe using custom properties or bpy.props) to have the function called just once? I can do it the "easy way", but it seems inefficient.

[My real application is a little more complicated than this. The procedure actually returns the location and rotation for one object and the Z translation for another.]

$\endgroup$
4
$\begingroup$

As you point out drivers need to return a float or int (bool). You can however add just about any type to the drivers namespace.

Not knowing the complete ins and outs of your setup you could look at setting up

i) A frame change handler and use something like globals, or an ID property to select objects and then set the location with your get_loc method.

def fc_handler(scene):
    obs = [o for o in scene.objects if "drive_me" in o.keys()]  
    for o in obs:  
        o.location = get_loc()

bpy.app.handlers.frame_change_pre.append(fc_handler)

ii) A mix of drivers and a frame_change handler, eg set up a "Mover" class and make an instance available to the driver_namespace, update it if needbe in a frame_change handler

class Mover():
    def setloc(self, frame):
        self.x, self.y, self.z = get_loc()
    def __init__(self):

        pass # set up a class

mover = Mover()
bpy.app.driver_namespace["mover"] = mover

Then properties can be driven with mover.x, mover.y or something more adventurous mover.jiggle(var1, var2)

Using the drivernamespace is a handy way to test things from the python console

>>> dm = bpy.app.driver_namespace['DriverManager']
>>> dm
<sound_drivers.sounddriver.DriverManager object at 0x7facfaa903c8>

For an interactive tool I would use a modal operator. Here is an edited snippet from script editor > templates > python > Operator Modal Timer, Op performs commands on timer tick or event. A scene bool property update method to start. To stop the operator set it to False. Eg use toggle button in a UI.

def toggle_modal_timer(self, context):
    if self.modal_timer:
        bpy.ops.wm.modal_timer_operator('INVOKE_DEFAULT')

bpy.types.Scene.modal_timer = BoolProperty(default=False, update=toggle_modal_timer)

def get(self):
    # here is a handy spot to return a manager class
    # using dns can declare it anywhere.
    # using context we have it available on context pass.
    dns = bpy.app.driver_namespace
    return dns.get('mover', None)

bpy.types.Context.controller = property(get)


class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        scene = context.scene
        controller = context.controller

        if not scene.modal_timer:
            return self.cancel(context)
        #print(event.type, dir(event))    
        if event.type in {'LEFTMOUSE'} and event.value == 'RELEASE':
            print("LEFT MOUSE RELEASE")

manipulating context.controller will effect any drivers with mover in their scripted expression. You can get "tricky" with the Mover class by passing a name reference and the locals() dictionary via drivers' scripted expressions.

Of course you can also manipulate objects directly in the operator.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Thanks, interesting ideas (and I think I actually understand them). I didn't want to bog down my description too much with details, but I should have mentioned that I want to use this as an interactive "tool" (e.g., as I move a empty), not in animation. Would bpy.app.handlers.scene_update_pre be appropriate? $\endgroup$ – Jabberwock Oct 11 '15 at 18:33
  • $\begingroup$ I generally go for a modal timer operator before scene_update handlers that are called around 200 times a second. Another "trick" is using the tool panel's draw method to manipulate python variables. $\endgroup$ – batFINGER Oct 11 '15 at 18:43
  • $\begingroup$ I looked through the documentation, but I can't figure out what you mean by a "modal timer". Could you point me to the proper functions, or to an example? Also (and this might simplify things), is there a way to have the function called when I release the mouse button? I think the movement might be too unstable for a fully-interactive mode. $\endgroup$ – Jabberwock Oct 15 '15 at 0:18
  • $\begingroup$ I've added an explanation to the answer. $\endgroup$ – batFINGER Oct 15 '15 at 11:16
  • $\begingroup$ Thanks. These aren't quite the solutions I was expecting, but it looks like they're the best that can be done, so I'll flag this as the answer. I'll probably wind up doing something like the modal timer (once I figure out exactly what it's doing). $\endgroup$ – Jabberwock Oct 15 '15 at 23:18

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.