1
$\begingroup$

I´m coming from 3ds max and I would like to rebuild a scene in blender. It has a controller that outputs the angle between a bone and the world z-axis via the dot product function. That´s pretty simple in 3ds Max with e.g

> myvec=$.transform.row1 globalz=[0,0,1] theAngle = acos(dot (normalize
> myvec) (normalize globalz))

The script controller calculates the angle between the bones X-axis(roll axis) and the world z-axis. This works for realtime tranformations and animations as well.

How could I make this with a blender driver?

So far I have written a script which is running in the console line by line(sorry for the noobish syntax)

import bpy
import mathutils
import math

from mathutils import Matrix, Vector

myvec=bpy.data.objects['Armature'].pose.bones['Bone'].matrix.col[1]
gloma= Matrix()
ori=gloma.col[2]
ori=ori.dot(myvec)
ori=math.acos(ori)
ori=math.degrees(ori)
print (ori)

After I had created a driver function with bpy.app.driver_namespace that driver didn´t got evaluated. Any help would be very appreciated.

$\endgroup$
2
  • $\begingroup$ There is no function in the script. So I wonder if you could add a function to the namespace. Could you share your file? $\endgroup$
    – tetii
    Commented Jun 4, 2020 at 7:47
  • $\begingroup$ You are right. For the driver function I had to put a "def()" function. After some more testing, I could say that it´s only a refresh issue. With a keyframe animated bone rotation, the function puts out the desired value when playing the animation. For the realtime feedback the "animaton node addon" is the route to go. $\endgroup$
    – Rex Manto
    Commented Jun 5, 2020 at 8:26

2 Answers 2

0
$\begingroup$

Script:

import bpy
import math

def test_fn2(pose_bone):
    rig = pose_bone.id_data
    mat = rig.matrix_world @ pose_bone.matrix
    vec = mat.col[1].normalized()
    angle = math.degrees(math.acos(vec[2]))
    return angle

bpy.app.driver_namespace["test_fn2"] = test_fn2

Settings:

Turn on in Text Editor. And Auto Run Python Scripts in User Preferences if you need.

Expression: test_fn2(bones["Bone"])/45

enter image description here

Additions:
Original script has two mistakes.
1. Transformation matrix for converting to world space is

object.matrix_world @ pose_bone.matrix

2. Vector myvec needs to be normalized.

$\endgroup$
0
$\begingroup$

Vector.angle(other)

There is also the mathutils.Vector.angle method to calculate the angle between two vectors. (The equiv of the inverse cosine of the normalized dot product)

Alternative driver setup.

enter image description here

Really like the setup in @tetii's answer, particularly passing posebone and using its id data to get the armature object

Instead of passing one variable the pose bone pb will use a variable mw the matrix world of the armature object (data path matrix_world), and another that is the matrix of the pose bone (datapath pose.bones["Bone"].matrix)

The idea here is to keep the script expression "manageable" by using mw in place of pb.id_data.matrix_world and pbm for pb.matrix.

Worth noting there is only so far that paths work eg couldn't get the axis pose.bones["Bone"}.matrix.col[2] to work as a driver variable datapath.

Still the expression is pretty small

(mw @ pbm.col[1]).to_3d().angle((0, 0, 1))

Where (mw @ pbm.col[1]) is the global space Y (down bone) vector, converted to 3d, and finally use angle method to return angle from global Z.

Always Test with a script

Ok have gone out of my way to remove the need for a text script method to contain the expression to the driver expression.

Strongly recommend using a driver namespace method to nut out, debug and test drivers. For complex expressions use a text script.

A handy little trick is passing locals() to a driver method, it's all the variables with the names you set. Also checking use self gives a way to see what object the driver is on, and finally stick in the datapath.

enter image description here

The expression is

test(self, locals(), "rotation_euler.x")

Script:

import bpy

def test(self, driver_locals, data_path=""):
    print(f"DR:{repr(self.id_data)}.{data_path}")
    for k, v in driver_locals.items():
        print(k, repr(v))
    return 0 # often use random() to indicate change

bpy.app.driver_namespace["test"] = test

Output

DR:Evaluated Object 'Cube.001'.rotation_euler.x
mw Matrix(((1.0, 0.0, 0.0, 4.08627986907959),
        (0.0, 1.0, 0.0, -2.97157883644104),
        (0.0, 0.0, 1.0, 0.36508551239967346),
        (0.0, 0.0, 0.0, 1.0)))
pbm Matrix(((0.9408926963806152, -0.33870482444763184, -4.5507988488679985e-08, 0.0),
        (-4.5507988488679985e-08, 7.941590496329809e-09, -1.0, 0.0),
        (0.33870482444763184, 0.9408926963806152, -7.941590496329809e-09, 0.0),
        (0.0, 0.0, 0.0, 1.0)))
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .