4
$\begingroup$

I need to orient a various number of arrows on the screen, each need to point to a direction calculated by Python.

Python have calculated the end points of each arrow (say tail at (0,0,0), tip at (1,1,1)). How do I place an arrow between those two points, with the tail and tip at the correct spot with scripts?

My current plan is place the tail, then calculate the degree of rotation needed to get to the right direction, then rotate it. Is there potentially an easier/more direct way?

Optional: If the code does not scale the arrow, that's fine, as long as the arrow point in the right direction.

$\endgroup$
2
$\begingroup$

Full credit should go to: align-object-to-vector-using-python for the API usage of Quaternion and to_track_quat.

downside to the following methods:

They aren't dynamic, unlike the proposed method that uses constraints.

Single Size Arrows

Model the arrow once, make duplicates :)

import bpy
import bmesh
import mathutils
from mathutils import Vector

scene = bpy.context.scene

arrow_mesh = bpy.data.objects['ArrowObject'].data

vectors = [
   [(0,0,1), (0,0,0)],
   [(0,1,0), (0,0,0)],
   [(1,0,0), (0,0,0)],
   [(1,1,1), (0,0,0)]   
]

for head, tail in vectors:
    v1, v2 = Vector(head), Vector(tail)

    # duplicate mesh into new object.
    obj = bpy.data.objects.new("Arrow_duplicate", arrow_mesh) 

    obj.location = v2
    obj.rotation_mode = 'QUATERNION'
    obj.rotation_quaternion = (v1-v2).to_track_quat('Z','Y')
    scene.objects.link(obj)

Be sure to have the Arrow's object origin on the tail, and coinciding with the axis of the Arrow. (just in-case)

enter image description here

It may be worth mentioning that this code won't stretch the arrow to exactly fit between head and tail vectors, but it will place the arrows tail/origin at exactly the tail vector and point to the correct head-vector. As soon as you need the arrow to fit exactly between the two vectors you introduce a different problem, with several solutions

Adaptive arrow scaling

description:

- A cone which you can orient given similar code
- A stem which takes orientation and scaling in its length-wise axis.

enter image description here

code:

import bpy
import bmesh
import mathutils
from mathutils import Vector

scene = bpy.context.scene
objects = bpy.data.objects

arrow_stem_mesh = objects['Arrow_stem'].data
arrow_cone_mesh = objects['Arrow_cone'].data
arrow_cone_height = 0.448  # for example..

vectors = [
   [(0,0,1), (0,0,0)],
   [(0,1,0), (0,0,0)],
   [(1,0,0), (0,0,0)],
   [(1,1,1), (0,0,0)],
   [(2,2,2), (3,3,3)],
   [(4,4,2), (2,2,2)],
   [(4,4,3), (2,0,0)]
]

for head, tail in vectors:
    v1, v2 = Vector(head), Vector(tail)

    # Scale the Stem, and add to scene
    obj = bpy.data.objects.new("Arrow_duplicate", arrow_stem_mesh)

    obj.location = v2
    obj.scale = (1, 1, (v1-v2).length)
    obj.rotation_mode = 'QUATERNION'
    obj.rotation_quaternion = (v1-v2).to_track_quat('Z','Y')
    scene.objects.link(obj)

    # orient Cone, and add to scene
    obj2 = bpy.data.objects.new("Arrow_duplicate", arrow_cone_mesh)
    obj2.location = v1   # start at tail and work back
    obj2.rotation_mode = 'QUATERNION'
    obj2.rotation_quaternion = (v1-v2).to_track_quat('Z','Y')
    scene.objects.link(obj2)

The .blend

(just hit run)

$\endgroup$
  • $\begingroup$ I will give it a try. Yes, I am modelling the arrow once and duplicating. It's just placing it. $\endgroup$ – Darth Alpha May 24 '15 at 18:15
3
$\begingroup$
  • Consider a [Track To] constraint which will rotate the arrow toward the location of a given target. The green arrow points to the red ball below, wherever the ball happens to be.

enter image description here

  • In the animation below we see a few alternatives with constraints and modifiers

enter image description here

  • The blue arrow is a curve in case you find the straight lines excessively boring. Circular cross section bevel. The curves has hook modifiers for the end points. A third point is added into the blender curve to introduce more [visual curve] into the blender curve. The visual curve is optional. The green cap is position with a [follow path] constraint. Of course this flexibility requires more work.
  • The yellow arrow is an array modifier fit to curve straight line
  • The large green arrow is the [track to] constraint as before
  • The hook constraints

enter image description here

$\endgroup$
  • $\begingroup$ That require another object right? Not just a point? It would mean I need to create an "point" object for every arrow I make---which can be somewhere from 10 to 50 depending on the situation. $\endgroup$ – Darth Alpha May 24 '15 at 17:57
  • 2
    $\begingroup$ You can create en empty object which will not render. Any object can be set to NOT render. If you can place an arrow, then you can place an empty. Blender can handle a few x "thousand" empties. Assuming your computer executes blender at all. The red ball above was for illustrative purposes ... you may substitute an empty. Then it will be easy to use all the other facilities of blender with less python if that suits your tastes $\endgroup$ – atomicbezierslinger May 24 '15 at 18:00
1
$\begingroup$

Could not get the above solution to work, so here is another one:

import bpy
import mathutils

# calculates a vector (x, y, z) orthogonal to v
def getOrthogonal(v):

    x = 0
    y = 0
    z = 0

    if v[0] == 0: # x-axis is 0 => yz-plane

        x = 1
        y = 0
        z = 0

    else:

        if v[1] == 0: # y-axis is 0 => xz-plane

            x = 0
            y = 1
            z = 0

        else:

            if v[2] == 0: # z-axis is 0 => xy-plane

                x = 0
                y = 0
                z = 1

            else:

                # x*v0 + y*v1 + z*v2 = 0
                x = 1 / v[0]
                y = 1 / v[1]
                z = -((1/v[2]) * 2)

    return (x, y, z)

def drawNormal(origin, normal):

    b3 = mathutils.Vector(normal)

    # find orthogonal basis vectors
    b2 = mathutils.Vector(getOrthogonal(b3))
    b1 = b2.cross(b3)

    # normalize
    b1.normalize()
    b2.normalize()
    b3.normalize()

    # build transformation matrix
    basis = mathutils.Matrix()
    basis[0].xyz = b1
    basis[1].xyz = b2
    basis[2].xyz = b3
    basis.transpose()
    basis.translation = mathutils.Vector(origin)

    # draw an arrow from normal
    bpy.ops.object.add(type='EMPTY')
    arrow = bpy.context.object
    arrow.name = 'arrow'
    arrow.empty_draw_type = 'SINGLE_ARROW'
    arrow.matrix_basis = basis

# tests
# ----------------------------------------

# given origin and normal
origin = (0, 1, 0)
normal = (0, 1, 1)

drawNormal(origin, normal)

'''
# given head and tail
head = mathutils.Vector((0, 0, 1))
tail = mathutils.Vector((0, 2, 0))

origin = tail
normal = head - tail

drawNormal(origin, normal)
'''

Explanation

This draws normals as single arrows, so no stretching either. I use a transformation matrix, and the way i build it is using the normal vector as 1 of its basis vectors, then calculate 2 random vectors orthogonal to this. So in the end i have 3 basis vectors. The getOrthogonal() can surely be done better too, but enough for me. It is based on the dot-product equation and could be actually done with some math library, not sure.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.