3
$\begingroup$

So,

Each of my polygons is a separate object. Their default normals point like this: enter image description here

But their orientation or local axes are like so: enter image description here

How can I make their local axes to be like so, but automatically? enter image description here

I guess particles might be one solution for this exact case (if you emit new objects on top of the existing ones) but let's say the original objects are triangles and I wouldn't want to change their shape and pointing direction at all, only their local orientation axes, would that be possible?

Cheers!

$\endgroup$
  • $\begingroup$ The most simple answer is no. It's not possible using commonly available built in Blender functions, but if you need it for transformations in edit mode, then setting Normal as transformation orientation will do the job. It's also possible via Python script. $\endgroup$ – Mzidare Sep 19 '18 at 17:43
  • $\begingroup$ You can hack it manually, by snapping a cube to the plane's face, with 'Align Rotation' switched on, Ctrl-J joining the plane to the (active) cube, and deleting the cube's part of the joined mesh in Edit Mode. But i guess that's not what you mean by automatic. $\endgroup$ – Robin Betts Sep 19 '18 at 19:27
  • $\begingroup$ Robin Betts - haha, thanks, I was just experimenting with that, it's nice that you can actually delete all the vertices or mesh data of an object and still use it for aligning and joining other objects into it. $\endgroup$ – Manu Järvinen Sep 19 '18 at 19:45
  • 1
    $\begingroup$ I think this procedure will be shortened in 2.8 - where the 3D cursor will have an orientation. $\endgroup$ – Robin Betts Sep 19 '18 at 23:07
1
$\begingroup$

Yay! I actually managed myself to make a brute-force artist-made stupid script that seemed to actually work well for the many types of situations one faces with 100s of different polygon-objects.

Usage:

  • Select an object with less polygons than 2000 (otherwise it takes ages, 500 polys takes like 30 seconds)

  • Run the script


# WARNING: This is quite a heavy script. It takes like 30-60 seconds to handle 512 faces alone.

## SETTINGS ##
PolygonName = "PolygonObject.000"
GroupName = "PolygonGroup"


## CODE ##
import bpy
bpy.ops.group.create(name=GroupName)
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_all(action = 'SELECT')
bpy.ops.mesh.mark_sharp()
bpy.ops.object.modifier_add(type = 'EDGE_SPLIT')
bpy.ops.object.mode_set(mode = 'OBJECT') 
bpy.ops.object.modifier_apply(apply_as = 'DATA', modifier="EdgeSplit")
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.separate(type = 'LOOSE')
bpy.ops.object.mode_set(mode = 'OBJECT') 

for obj in bpy.context.selected_objects:
    bpy.ops.mesh.primitive_cube_add(layers=(True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True))
    #bpy.context.active_object.show_axis = True
    bpy.context.active_object.name = "NormalAxisObject"
    bpy.ops.object.mode_set(mode = 'EDIT')
    bpy.ops.mesh.delete(type ='FACE')
    bpy.ops.object.mode_set(mode = 'OBJECT') 
    bpy.context.active_object.rotation_mode = 'YXZ'    

    bpy.context.scene.objects.active = obj
    bpy.ops.object.particle_system_add()
    obj.particle_systems['ParticleSystem'].settings.type = 'HAIR'
    obj.particle_systems['ParticleSystem'].settings.use_advanced_hair = True
    #obj.particle_systems['ParticleSystem'].settings.hair_length = 1
    obj.particle_systems['ParticleSystem'].settings.count = 1
    obj.particle_systems['ParticleSystem'].settings.userjit = 1
    obj.particle_systems['ParticleSystem'].settings.render_type = 'OBJECT'
    obj.particle_systems['ParticleSystem'].settings.dupli_object = bpy.data.objects['NormalAxisObject']
    obj.select = True
    bpy.ops.object.duplicates_make_real()
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects['NormalAxisObject'].select = True
    bpy.ops.object.delete()

    bpy.data.objects['NormalAxisObject.001'].select = True
    bpy.context.scene.objects.active = bpy.data.objects['NormalAxisObject.001']
    #bpy.context.object.rotation_euler[0] = bpy.context.object.rotation_euler[0] - 1.57079633 # put Z as the up axis
    bpy.ops.object.select_all(action='DESELECT')

    obj.select = True
    bpy.data.objects['NormalAxisObject.001'].select = True
    bpy.context.scene.objects.active = bpy.data.objects['NormalAxisObject.001']
    bpy.ops.object.join()
    bpy.ops.object.transform_apply(scale=True)
    bpy.data.objects['NormalAxisObject.001'].name = PolygonName
    bpy.ops.object.group_link(group=GroupName)

bpy.ops.object.select_same_group(group=GroupName)
$\endgroup$
0
$\begingroup$

If you calculate the right rotation matrix and transform the vertices with the inverse of that matrix while assigning the object transforms with the Euler angles of the matrix, then the axis will point in the right direction while the meshes haven't moved.

result

As a prerequisite you have to specify how exacly the new axes should be aligned. If we assume that the z axis points into the direction of the normal then we need to find a way to define the rotation of the x and y axes around the z. In the example script at the bottom I assume that the x axis should point to the midpoint of the two vertices with the highest x value. An alternative could be that you're taking the average of the vertices with the correct vertex indices, a single vertex at random or maybe the vertex with the greatest distance to the center for an irregular polygon.

The rotation matrix then is given by

from mathutils import Matrix

rot = Matrix.Identity(3)
rot[0] = guide                # x
rot[1] = normal.cross(guide)  # y
rot[2] = normal               # z
rot = rot.transposed()

where normal is the polygon's normal and guide is a normalized vector to uniquely define the rotation around the normal.

Once you have that matrix, you can easily rotate the mesh (with the matrix's inverse) as well as assign the object's transforms to counter that:

import bpy
from mathutils import Matrix

objs = [obj for obj in bpy.data.objects if obj.name.startswith('Plane')]

for obj in objs:
    verts = obj.data.vertices
    normal = obj.data.polygons[0].normal

    sorted_verts = sorted((v.co for v in verts), key=lambda co: co.x, reverse=True)
    guide = (sorted_verts[0] + sorted_verts[1]).normalized()

    # Vector rejection in case the guide isn't perpendicular to the normal
    # e.g. when the object origin isn't in the polygon's plane
    guide_projection = guide.dot(normal) * normal
    guide_rejection = guide - guide_projection
    guide = guide_rejection.normalized()

    rot = Matrix.Identity(3)
    rot[0] = guide
    rot[1] = normal.cross(guide)
    rot[2] = normal
    rot = rot.transposed()

    for v in verts:
        v.co = rot.inverted() * v.co

    obj.rotation_euler = rot.to_euler()

N.B.: My script will fail if a plane is rotated so that the normal points in the global x axis and the sorting function will pick to vertices opposite of each other so that the guide vector is zero. Production code would probably get a better sorting function ;-)

$\endgroup$
  • $\begingroup$ Wow! Thanks! Worked wonderfully for a couple of isolated planes! But then, if I apply this for a larger experiment, say, an Icosphere that has all its polygons separated as individual objects (mark all edges as sharp, edgesplit modifier, apply, separate by loose parts, pivots centered to geometry), and then the script is run: all the faces nudge a little bit in their rotation :/ - I actually managed myself to make a brute-force artist-made stupid script that seemed to actually work better for the many types of situations one faces with 100s of different polygon-objects. I'll post it here $\endgroup$ – Manu Järvinen Sep 19 '18 at 23:28
  • $\begingroup$ I'm glad you got it working. But just out of curiosity: How much nudging are you talking about? I just tested the script with an icosphere and detected no visible wiggle. Initially I assumed there are rounding errors with the matrix calculations, and some additional normalization, e.g. for the cross product, would help. But unfortunately (or fortunately?) I can't notice a bug. $\endgroup$ – binweg Sep 20 '18 at 6:40
  • $\begingroup$ Hey! That’s wonderful news! I rather use and accept your method than my hackiest hack if it works. I’ll see if I can reproduce the nudging and post an image of it soon. I try both Win, Mac and Linux $\endgroup$ – Manu Järvinen Sep 20 '18 at 6:46
  • 1
    $\begingroup$ Alright! So, for Linux and Windows it does this: i.imgur.com/ptAhzAm.gif $\endgroup$ – Manu Järvinen Sep 20 '18 at 11:00
  • 1
    $\begingroup$ AHA! When setting the pivot points I did "Origin to Geometry" for that GIF. And the script works nicely if I do "Origin to Center of Mass (Surface)", for example :) $\endgroup$ – Manu Järvinen Sep 20 '18 at 11:05

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.