# Python - Orient light to polygon (face normal as direction)

I am currently trying to write a script that creates a light at every polygon oriented to each polygon. The script takes as an input the active mesh. It iterates over the polygons of the mesh and gets their global position. It then creates a light at that position. The problem is that I cannot get the light to be rotated so that they are perpendicular to the face, is the same as the normal. I tired using the function polygon.normal but this does not give me a vector I can apply as a rotation. Instead .normal gives me a local position vector. It is local in the sense that it sees the polygon center as 0, 0, 0, basically the .normal function is no good for me in this case.

Here is what my script currently does:

Here is what I want it to do: I do not care about the yaw of the light only the orientations matters to me atm.

part of the script or download here also included in the blend file The code is well documented I think, let me know if anything is unclear. The function for creating the lights starts at line 47.

def create_light_at_face():
"""
layers: tuple. 20 booleans where exaclty one boolen is True
return: None
creates an area light the has the same orientation as the normal at the center of each face
"""
for poly in mesh.polygons:
loc = get_global_poly_pos(poly)
normal = get_normal(poly)  # this is probably needed somehow

# get the rotation needed for each polygon face, idk how
# default values in RADIANS, actual values will be computed somehow
rot_x = 0
rot_y = 0
rot_z = 0
bpy.ops.object.lamp_add(type='AREA', view_align=False, location=(loc[0], loc[1], loc[2]), rotation=(rot_x, rot_y, rot_z), layers=get_layers())
# scale lamp up a little fo we can see the area
bpy.context.object.data.size = 3


There's probably a cleaner solution, but for the time being, you can use:

import bpy
from mathutils import Vector, Matrix

obj = bpy.context.active_object
mesh = obj.data

def get_layers():
"""
return: tuple of 20 booleans. True if layer is active layer,False if not
Allows the mesh to be created on the active layer. There is only 1 active layer
"""
active_layer = bpy.context.scene.active_layer
layers = [False] * 20
layers[active_layer] = True
return tuple(layers)

def create_light_at_face():
"""
layers: tuple. 20 booleans where exaclty one boolen is True
return: None
creates an area light the has the same orientation as the normal at the center of each face
"""
mat_world = obj.matrix_world
up = Vector((0,0,1))

for poly in mesh.polygons:
co = mat_world * Vector(poly.center)

forward = poly.normal.copy()
forward.rotate(mat_world)
right = forward.cross(up).normalized() # Vector.length closer to 1.0
up = right.cross(forward)

rot = Matrix((right, up, -forward)).transposed().normalized().to_4x4()
mat = Matrix.Translation(co) * rot

# scale lamp up a little fo we can see the area
bpy.context.object.data.size = 3
bpy.context.object.matrix_world = mat

create_light_at_face()


Also tried eulers, but did not work reliably (some lamps pointing inwards).

As ideasman_42, suggested, to_track_quat() can be used to do the job, too:

        quat = poly.normal.to_track_quat('-Z', 'Y')
loc = Matrix.Translation(poly.center)
mat = mat_world * loc * quat.to_matrix().to_4x4()
bpy.context.object.matrix_world = mat


It's not much code less, but more understandable I hope.

Another variant:

        loc = mat_world * Vector(poly.center)
# use loc as co for lamp_add operator here
quat = poly.normal.to_track_quat('-Z', 'Y')
quat.rotate(mat_world)
bpy.context.object.matrix_world *= quat.to_matrix().to_4x4()

• Note, constructing the orientation matrix is interesting to do manually, but you could use blender.org/documentation/blender_python_api_2_69_release/… Feb 19 '14 at 22:26
• Thanks for hint, I got it to work after a while. Added two variations for to_track_quat() Feb 19 '14 at 23:52

To manually duplicate a light onto every face, oriented to the normal, is rather simple, parent the light to the object and enable DupliFaces. Make Duplicates Real can then turn the duplicates into individual objects.

That can also be easily duplicated using Python -

import bpy

replicator = bpy.context.active_object