I want to rotate a single polygon so that it faces a point in space, ie the 3Dcursor or vert.co. So that the normal of the polygon faces the point.

Reading other Questions; to_track_quat(track, up) should give me the Quaternion rotation I need if I first multiply it by the faces normal "it might not be facing up".

The problem is how do I rotate a polygon by a value, so that its normal faces something?

face needs to face the 3DCursor

enter image description here


3 Answers 3

import bpy
from mathutils import Matrix

def turn_face_to_point(obj, face, target):
    mat_world = obj.matrix_world

    #transform the face to world space
    #to take non-uniform scaling into account
    #which may change the angle of face.normal
    for index in face.vertices:
        vert = obj.data.vertices[index]
        vert.co = mat_world @ vert.co

    #get the rotation difference
    track  = empty.location - face.center
    q = face.normal.rotation_difference(track)

    #compose the matrix
    #rotation around face.center in world space 
    mat = Matrix.Translation( face.center) @ \
          q.to_matrix().to_4x4() @ \
    #transform the face back to object space afterwards      
    mat_obj = mat_world.inverted() @ mat

    #apply the matrix to the vertices of the face
    for index in face.vertices:
        vert = obj.data.vertices[index]
        vert.co = mat_obj @ vert.co

obj   = bpy.data.objects['Plane']
empty = bpy.data.objects['Empty']
face  = obj.data.polygons[0] 

turn_face_to_point(obj, face, empty.location)
  • $\begingroup$ Thank you this works perfectly, I have learned a lot from this script. $\endgroup$
    – MCHammond
    Jun 20, 2014 at 22:00
  • $\begingroup$ Made a change to the script so it works with non-uniform-scaling. $\endgroup$ Jun 21, 2014 at 10:01
  • $\begingroup$ it looks like there's a mismatch between the input arguments and the variables used in the function (specifically "target" and "empty"). Can you describe why do you need 3 arguments for that function? The question was asking about rotating a face to a point. What's the purpose of the 3rd argument? $\endgroup$
    – aarslan
    Nov 9, 2015 at 19:34

I don't know why scaling is not taken into account (since you can apply scale it is not a real restriction.

To test this create a plane and run the script, set your cursor to some other location and run again.

Note: this doesn't work properly if the polygon is parented.

enter image description here

import bpy
from bpy import context
import mathutils
from mathutils import Matrix
import math

def track_to_point( obj, point ):
    normal = obj.data.polygons[0].normal.xyz
    mat_obj = obj.matrix_basis
    mat_scale = mathutils.Matrix.Scale(1, 4, mat_obj.to_scale() )
    trans = mat_obj.to_translation()
    mat_trans = mathutils.Matrix.Translation(trans)
    print( "mat_scale\n" + str(mat_obj.to_scale()))
    point_trans = point -trans
    q = normal.rotation_difference( point_trans )
    mat_rot = q.to_matrix()

    mat_obj = mat_trans * mat_rot * mat_scale    
    obj.matrix_basis = mat_obj

plane = bpy.data.objects['Plane']
point = bpy.context.scene.cursor.location

track_to_point( plane, point )

Parenting is explained in detail here

Roles of matrices

  • matrix_basis

Matrix access to location, rotation and scale (including deltas), before constraints and parenting are applied

  • matrix_local

Parent relative transformation matrix

  • matrix_parent_inverse

Inverse of object’s parent matrix at time of parenting

  • matrix_world

Worldspace transformation matrix

  • $\begingroup$ let me know when you need more explanation on how this works. $\endgroup$
    – stacker
    Jun 19, 2014 at 19:38
  • $\begingroup$ Had another try and this seems to work in 2.70 pasteall.org/52356/python . $\endgroup$ Jun 20, 2014 at 6:24
  • $\begingroup$ @pinkvertex nice, but in its current form it seems only to flip the normal of the plane but takes parenting into account, I will try to get parenting working. Do you have an idea why the matrix.to_scale() returns an identity? $\endgroup$
    – stacker
    Jun 20, 2014 at 6:37
  • $\begingroup$ ? I used this .blend file to test it and it tracks the empty's position. BTW: You are tracking point_trans = point - trans, where trans is mat_obj.to_translation(), which is the object's origin in world space. You likely want to use the center of the face converted to world space instead. If the object is not scaled it should return an identity. $\endgroup$ Jun 20, 2014 at 7:20
  • $\begingroup$ @pinkvertex Now I got it you're tracking to the empty not the 3D cursor, why didn't you update your answer? $\endgroup$
    – stacker
    Jun 20, 2014 at 8:32

First, let's assume that a face is only "facing" a destination vertex if a point in that face has a normal vector that passes through that destination vertex (otherwise it's hard to tell what you mean by a plane "facing" a point).

In your picture, you have a line connecting a point in the middle of your polygon to the 3d cursor. You need to make a vector out of that line, and then find the rotation_difference() between the normal of your face and that line. Once you have the rotation difference, rotate your polygon by that amount (you need to rotate with the "center point" of your face as it is in your screenshot at the center...so you may need to move your face to the local object origin, rotate it there, and then move it back after it has been rotated).

  • $\begingroup$ "Once you have the rotation difference, rotate your polygon by that amount" how would you do this? I cant find any information about rotating a polygon in edit mode. Thank you $\endgroup$
    – MCHammond
    Jun 19, 2014 at 17:55

You must log in to answer this question.

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