10
$\begingroup$

I would like to use Blender to visualize what I'm calculating with linear algebra formulas, I need to perform linear functions starting with a 2D or 3D vector and multiply this entity with a custom defined matrix; the result of this is a vector that I would like to visualize in some sort of way on the scene ( an arrow, a cilinder, a 3D shape a 2D shape, something ... possibly with colors so I can visualize multiple vector on the same scene ).

Now I know how to get the x,y,z coordinates for each vertex,

# 1 possible way to get x 
bpy.context.object.data.vertices[0].co.x

I don't know how to get an object of type Vector out of this ( 2D or 3D vector ) and fill a matrix ( tipically a 3x3 matrix ) that I can use to perform the computation.

I'm basically having a little trouble understanding how to get the types that I want for the task that I need to perform.

And in the end I also need to keep the result of this computation, tipically an array of vectors/vertices, on the scene in an easy-to-visualize way.

I'm using Blender 2.68a.

Thanks.

$\endgroup$

4 Answers 4

6
$\begingroup$

The documents for the Matrix and Vector classes are invaluable reading.

from mathutils import Vector

# zero vector, a default
v1 = Vector()

# quick-hand reference to the class.
print(dir(v1))  

# using integers or floats   
v2 = Vector((1, 0, 0))  
v3 = Vector((1.0, 3.0, 0.0)) 

x, y, z = v3.to_tuple()

v3.xyz
# Vector((1.0, 3.0, 0.0))

v3.zyx
# Vector((0.0, 3.0, 1.0))

# normalize the vector in place, overwrites the value of original vector
v3.normalize()

# normalized returns a normalized copy but does not modify the original.
v3.normalized()

There are many more methods in the Vector class, all covered in the documentation. Unfortunately there are no ready made tools for blender that will draw the head and tail of vectors in the way that you describe. I can see how they would be useful for teaching about vectors initially.

Math Viz addon, doesn't have much documentation but it is straight forward enough. Enable the addon then use the python console to enter these one by one while observing what happens in the 3d view.

v1 = Vector()
v2 = Vector((1,1,0))
line = [v1, v2]   # brackets are optional

If you want to delete any you can do del v1 or del line

$\endgroup$
6
$\begingroup$

I would suggest to add empty objects, or create a mesh to visualize Matrix/Euler/Vector types.

As a handy alternative to this, Blender comes with the MathVis addon that draws python defined variables in the view-port.

$\endgroup$
2
  • $\begingroup$ what is the namespace for this types ? $\endgroup$
    – user1154
    Aug 23, 2013 at 7:54
  • $\begingroup$ @user1154, For MathVis - the consoles global namespace is currently used. $\endgroup$
    – ideasman42
    Aug 23, 2013 at 9:42
3
$\begingroup$

I realize this is an old question, but I came across it. The other answers point to the MathVis addon, certainly a good tool, though I did not know it before.

My personal use case, coming from the python side, was more on the "visualize" part of the original question: I want to animate rigid body kinematics (which I get from actual measurements) and add visual arrows to represent all kinds of vectors.

Here's what I came up with; I acknowledge it is only sloppily integrated with blender. Maybe it still helps someone. (if you need colors, simply link a material to the shaft/tip)

import bpy
import numpy as np


class PhysVector(object):
    # a physics vector visualization (e.g. Forces, Moments, velocities...)
    
    def __init__(self, label, radius = 0.1, tip_width = 1.5, tip_length = 2., collection = 'Collection'): 
        # prepare the components of the vector: a shaft (cylinder) and a tip (cone)
        # the apex of the tip will be at the specified location (see SetPosition)
        # input: 
        #   label -> a label (will be the name of the object)
        #   radius -> the radius of the shaft
        #   tip_width -> the width of the tip, as multiples of radius
        #   tip_length -> the length of the tip, as multiples of radius
        # 
        
        # a name for the arrow
        self.label = label
        
        ## (0) the PhysVector group
        # preparing a group to store shaft and tip
        self.group = bpy.data.objects.new(self.label, None) # empty data object
        self.group.name = self.label # correct name display
        bpy.data.collections.get(collection).objects.link(self.group) # link the arrow in a collection
        self.group.rotation_mode = 'QUATERNION' # set rotation mode; I recommend quaternions.
        
        # lock the scaling, because it would affect vector length
        self.group.lock_scale = [True]*3

        
        # the tip length will be needed on position update
        self.tip_length = tip_length*radius
        
        
        ## (1) the shaft
        # always a head_length shorter than one.
        bpy.ops.mesh.primitive_cylinder_add(radius=radius \
                                            , depth=1.-self.tip_length \
                                            , location=(0, 0, 0.5-self.tip_length/2) \
                                            )
        self.shaft = bpy.context.selected_objects[0]
        
        # name the shaft
        bpy.context.active_object.name = f'{self.label}_shaft'
        
        # set rotation mode (because quaternions are cool)
        self.shaft.rotation_mode = 'QUATERNION'
        
        # set the origin to the base of the shaft.
        bpy.context.scene.cursor.location = (0., 0., 0.) # (by using the world cursor)
        bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
        
        # add the shaft to the group
        self.shaft.parent = self.group
        # moved and rotated with the group
        self.shaft.lock_location = [True]*3 
        self.shaft.lock_rotation_w = True
        self.shaft.lock_rotation = [True]*3


        ## (2) the tip
        # as with the shaft, but filling the space to unit length
        bpy.ops.mesh.primitive_cone_add(  radius1=tip_width*radius \
                                        , radius2=0 \
                                        , depth = self.tip_length \
                                        , location=(0, 0, 1.-(self.tip_length/2)) \
                                        )
        self.tip = bpy.context.selected_objects[0]
        
        # naming
        bpy.context.active_object.name = f'{self.label}_tip'
        
        # rotation mode (quaternions are for geeks)
        self.tip.rotation_mode = 'QUATERNION'
        
        # set the origin of the tip to the apex (via world cursor)
        bpy.context.scene.cursor.location = (0., 0., 1.)
        bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
        
        # add the tip to the group
        self.tip.parent = self.group
        # moved and rotated with the group
        self.tip.lock_location = [True, True, False]
        self.tip.lock_rotation_w = True
        self.tip.lock_rotation = [True]*3

        # reset cursor position
        bpy.context.scene.cursor.location = (0., 0., 0.)
        
        # store position
        self.position = [(0.,0.,0.), (0.,0.,1.)] # (to avoid keyframe overwriting)
        
        ## initialization done!

        
    def SetPosition(self, p0, p1):
        # set the position of the vector, given by a base and tip point
        # The arrow will increase/decrease length, but radius and head length are fixed.
        # input:
        #   p0 -> the base ponit
        #   p1 -> the tip (apex) point
        
        # update stored position
        self.position = [p0, p1]
        
        # the vector is avtually the difference between tip and base 
        vec = np.subtract(p1, p0)
        # a reference is needed to get the rotation quaternion
        ref = np.array([0., 0., 1.])

        
        ## (i) position
        # move the whole group, by setting its location
        self.group.location = p0
        # ... and adjust the tip relative position
        self.tip.location = (0., 0., np.linalg.norm(vec))     
        
        
        ## (ii) scaling
        # adjust the tip length; too short vectors are smaller.
        tip_length = np.min([self.tip_length, np.linalg.norm(vec)])
        self.tip.scale = [tip_length/self.tip_length]*3
                        
        # set the shaft length (originally little less than 1.0) by adjusting the scale
        # if vector is too short, radius is scaled down (as with tip)
        shaft_length = (np.linalg.norm(vec)-self.tip_length)/(1-self.tip_length)
        self.shaft.scale = (tip_length/self.tip_length, tip_length/self.tip_length \
                            , np.max([0., shaft_length]) \
                            )
        
        
        ## (iii) rotation
        # did I mention how awesome quaternions are?
        # this is just one of the many unit quaternions which will rotate the ref vector to the desired position.
        q_w = np.linalg.norm(ref) * np.linalg.norm(vec) + np.dot(ref, vec)
        q_x, q_y, q_z = np.cross(ref, vec)
                
        # with that, the whole vector is rotated.
        self.group.rotation_quaternion = (q_w, q_x, q_y, q_z)



    def SetKeyframe(self, frame_nr = None):
        # insert a keyframe with the current vector orientation
        
        if frame_nr is not None:
            # first, go to the specified frame number
            bpy.context.scene.frame_set(frame_nr) 
            # changing frame will reset the configuration; restore it:
            self.SetPosition(*self.position)
            
        # the group controls location and rotation
        self.group.keyframe_insert(data_path = 'location', index = -1)
        self.group.keyframe_insert(data_path = 'rotation_quaternion', index = -1)
        
        # the elements only change in certain aspects
        self.shaft.keyframe_insert(data_path = 'scale', index = -1)
        self.tip.keyframe_insert(data_path = 'location', index = -1)
        self.tip.keyframe_insert(data_path = 'scale', index = -1)
        
        # note: when animation starts/ends at very short vectors (vsv), the scaling
        #       of the shaft radius will be visible. To avoid that,
        #       (a) insert many intermediate keyframes or 
        #       (b) fix the x/y scaling in "SetPosition" above (but then the tip will flatten).



if __name__ == "__main__":
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)

    vm = PhysVector(label = 'v1', radius = 0.1, tip_width = 1.5, tip_length = 2.)
    
    # animation example
    bpy.context.scene.frame_end = 50

    vm.SetPosition((0.4, 0., 0.2), (-0.6, 0., 1.2))
    vm.SetKeyframe(0)
    
    # next frame
    vm.SetPosition(np.array((0.4, 0.2, 0.6)).ravel(), np.array((4.4, -0.2, 0.2)).ravel())
    vm.SetKeyframe(24)

    # test short vector
    vm.SetPosition((0.0, 0., 0.0), (0.05, 0.05, 0.05))
    vm.SetKeyframe(50)

$\endgroup$
1
$\begingroup$

If you wanna fill a matrix with a location vector, then it needs to be 4x4 transformation matrix of course (3x3 is used for rotation, CryEngine uses 3x4 for loc+rot, 4x4 is loc+rot+scale).

There's a utility function to create a 4x4 translation matrix from a 3d vector:

Matrix.Translation(bpy.context.object.data.vertices[0].co)

$\endgroup$

You must log in to answer this question.

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