Use frame change handler
Drivers are designed, and best used, where the field you add a driver to is the value the driver controls. It seems the way you use a driver is add it to some field where you're not actually interested in the value returned from the custom function added to the driver namespace, but rather use the side effects. Since adding a custom function to a driver namespace requires you to run a script after opening the .blend file each time, with its downsides, you can as well use a standard technique with the same downsides, that is actually intended for such purposes. So instead of:
import bpy
from math import sin, cos, atan2
def test():
ob = bpy.data.objects['Cube']
for co in (v.co for v in ob.data.vertices):
angle = atan2(co.y, co.x) + .05
co.x = cos(angle) * 2**.5
co.y = sin(angle) * 2**.5
bpy.app.driver_namespace['test'] = test
You can do:
import bpy
from math import sin, cos, atan2
def test(scene, depsgraph=None):
ob = bpy.data.objects['Cube']
for co in (v.co for v in ob.data.vertices):
angle = atan2(co.y, co.x) + .05
co.x = cos(angle) * 2**.5
co.y = sin(angle) * 2**.5
listeners = bpy.app.handlers.frame_change_pre
listeners.clear() # remove old handlers before adding updated versions
listeners.append(test)
(1 last line changes to 3 last lines; added arguments to the function so it's compatible with a listener interface)
Of course there may be better ways to approach your problem. For a cube or other simple meshes you could create a shape keys for each vertex (and for each axis perhaps) and then control the shape keys with drivers. This could all be set up with the Python script, that would only need to be run once, and then you would have a setup you can easily share without others needing to run a Python script or trust you.
And of course geometry nodes is a recent possibility that should work for something as simple as moving vertices.
If you don't want it for an animation
Here's another handler, that can be used without animation:
import bpy
from math import sin, cos, atan2
from bpy.types import SpaceView3D
last_value = None
def test():
global last_value
ob = bpy.data.objects['Cube']
# if ob['prop'] == last_value:
if ob.location.xyz == last_value:
return
for co in (v.co for v in ob.data.vertices):
angle = atan2(co.y, co.x) + .05
co.x = cos(angle) * 2**.5
co.y = sin(angle) * 2**.5
last_value = ob.location.xyz
# last_value = ob['prop']
try:
SpaceView3D.draw_handler_remove(SpaceView3D.my_handler, 'WINDOW')
except (AttributeError, ValueError):
pass
SpaceView3D.my_handler = SpaceView3D.draw_handler_add(test, (), 'WINDOW', 'PRE_VIEW')
I added a custom property (with default name) to the object, and update the vertices only if it changes (though the position of vertices don't depend on it, they probably should... So e.g. angle = ob['prop']
) - keep in mind you need to add a custom property to the object to make it work:
Of course you don't need to use a custom property, and as visible in Jhon's Paul edit, you can instead use something like location
...
You could also use Message Bus but I often find it problematic (e.g. with custom properties).