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)