5
$\begingroup$

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:

enter image description here

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.

enter image description here

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
$\endgroup$

2 Answers 2

8
$\begingroup$

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

import bpy
from math import radians
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

        bpy.ops.object.lamp_add(type='AREA', view_align=False, location=co, layers=get_layers())
        # 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()
$\endgroup$
2
1
$\begingroup$

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

bpy.ops.object.lamp_add(type='AREA', location=(0,0,0))
light = bpy.context.active_object

light.parent = replicator
replicator.dupli_type = 'FACES'

replicator.select = True
bpy.ops.object.duplicates_make_real()
$\endgroup$

You must log in to answer this question.

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