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.