Transfer the normal orientation to the local orientation

Hi

I’m beginner in scripting.

I would like to create a script which transfer (or set) the normal orientation in edit mode to the local orientation.

In edit mode because I would like to get the average of normals of a selection of polygons.

Like on this print-screen :

I know it’s possible to do it manually with a custom orientation and then with the “Align to Transform Orientation”.

But I want to create a batch to process hundreds of objects.

Actually, I would like to create a script like this:

  1. Set transform orientation to normal (in viewport)

  2. For each object in selection (loop)

  3. switch to edit mode = ON (polygon mode)

  4. deselect all polygons

  5. search and select polygons with more than 4 sides

  6. transfer the values of the normal orientation to the local orientation

  7. switch to edit mode = OFF

I started with something like that :

import bpy
bpy.context.scene.transform_orientation_slots[0].type = 'NORMAL'
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_face_by_sides(number=4, type='GREATER')

But then after I don’t know how to do

Thanks for your help !

It’s to process structures like this where all parts have wrong orientation

But then I will be able to get an average of the orientation with a script as described above

Regarding step 6, you can get a selected polygon’s normal this way:

import bpy
for o in bpy.context.selected_editable_objects:
    for p in o.data.polygons:
        if p.select == True:
            print(p.normal)

And to get that polygon’s vertices normals:

import  bpy
for o in bpy.context.selected_editable_objects:
    for p in o.data.polygons:
        if p.select == True:
            for v in p.vertices:
                print(o.data.vertices[v].normal)

I believe I can finish making a script for you, but I need an answer to one question that I have: When you say, "location rotation," do you mean a polygon's location rotation or its vertices location rotation?

Hello @RPaladin,

Thanks for your help :slight_smile:

My apologies, I was tired when I wrote my question/request and I found a mistake in the print-screen (I updated it).

I think maybe my request was not very clear

I didn’t say : “location rotation”

I’m speaking about “LOCAL ORIENTATION”.

So the orientation of the axes of an object.

By default there are several ways to display the orientation of the pivot of an object : GLOBAL / LOCAL / NORMAL …

2022-08-12_09h22_06

If I’m in NORMAL mode, in EDIT MODE, and I select some faces → Then Blender do automatically an average of the normals dans display the axes oriention accordingly the the noramls average. (like on the print-screen below).

I would like to get (copy) the already computed orientation of the axes (in NORMAL mode) and transfer (past) it to the LOCAL mode

Just a transfer of what blender already computed.

OK i made a very ugly and amateur script but it works well for the purpose I need.

Maybe someone could help me to make it nicer ?

import bpy

sel_objs = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']

for obj in sel_objs: 
    obj.select_set(False) 

while len(sel_objs) >= 1:         
    obj1 = sel_objs.pop() 
    obj1.select_set(True) 
    bpy.context.view_layer.objects.active = obj1
    bpy.context.scene.tool_settings.use_transform_data_origin = False
    bpy.context.scene.transform_orientation_slots[0].type = 'NORMAL'
    bpy.ops.object.mode_set( mode = 'EDIT' )
    bpy.ops.mesh.select_mode( type = 'FACE' )
    bpy.ops.mesh.select_all( action = 'DESELECT' )
    bpy.ops.mesh.select_face_by_sides(number=4, type='GREATER')
    bpy.ops.transform.create_orientation(name='tmp', use_view=False, use=False, overwrite=True)
    bpy.context.scene.transform_orientation_slots[0].type = 'tmp'
    bpy.ops.object.mode_set( mode = 'OBJECT' )
    bpy.context.scene.tool_settings.use_transform_data_origin = True
    bpy.ops.transform.transform(mode='ALIGN', orient_type='tmp', orient_matrix_type='tmp')
    bpy.context.scene.tool_settings.use_transform_data_origin = False
    bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL'
    obj1.select_set(False)

the script set the local orientation of each selected mesh based on the orientation of normals of a selection of polygons.

the selection is made with polygons with more than 4 sides.

1 Like

This script dosnt seem to be working in the latest blender, or im not using it right

I have an imported scene from an old download, i have 1000s of objects many that are duplicates
Everything is imported as if transformations have been applied so position and rotation are all 0,0,0

Position can be fixed with ‘origin to geometry’s mass’ to re-centre origins for all objects

I just need a fix for rotation, if i can get a script to rotate the local axis based on each objects data(normals or and vert data) All objects that are actually the same object would have the same vert count face count and all face normals would be the same.

Its just there local rotations that are all different or rather there all set to one global rotation. If a script can set the local axis to some sort of calculated direction based on the information of the mesh then this should produce the same result on all duplicate meshes, it should produce the same axis direction relative to the mesh regardless of its current oration in the scene.

The script could then go through and link all duplicate Objects(replace with linked duplicate)
Thus instancing the scene of 1000s of objects, ideally the script is run with nothing selected and it just processes all objects in the scene, fixing local axis and linking none linked duplicates

I have this script i found

import bpy
from bpy import context as C, data as D
from mathutils import Vector

if C.mode != 'EDIT_MESH':
    raise Exception("Go to Edit Mode and select the face(s) to point towards XZ plane")

bpy.ops.object.mode_set(mode='OBJECT')
alphaape = C.object
indices = [f.index for f in alphaape.data.polygons if f.select]


def get_rot(mesh, indices):
    normals = [mesh.polygons[i].normal for i in indices]

    # no need to divide by len(normals) or normalize
    averaged_dir = sum(normals, start=Vector()) 
    averaged_dir.z = 0  # this way the normal is parallel to Z=0 plane and
                        # hopefully perpendicular to Y=0 plane
    rot = averaged_dir.to_track_quat('-Y', 'Z').to_euler()
    return rot

apes = (o for o in D.objects if o.name.startswith(''))

for monke in apes:
    if monke is alphaape:
        continue
    dupmesh = monke.data
    rot = get_rot(dupmesh, indices)
    print(rot)
    monke.data = alphaape.data
    D.meshes.remove(dupmesh)  # WARNING! I'm not checking for other users!
                              # - it's safer to not remove and make it an orphan
    monke.rotation_euler = rot

Its not quite working
It dosnt really recognise same meshes and also errors out if there is a mesh thats not the same as the target mesh, It also has issues with rotations on x and y axis, duplicate objects that are rotated on the x or y is basically ignored and only z rotations are used when the linked duplicate is placed

An alternate idea is a script that takes a selected mesh looks for identical meshes and tries to place a linked copy of the selected object in all the locations of the identical meshes(removing the identical mesh a it goes). Where it tried to place the object snapping verts and rotating until all verts of the selected object match the target object


As if i was trying to place and rotate the object by hand to match another without looking at the transform numbers. So it would be a way to replace with a linked duplicate that dosnt rely on origin data to do it, just snaps the actually geometry to another

Working for me in Blender 4.1

I wanted the mesh to reset its orientation after, so just added an extra line to do that, I also stored the original options and restored them after and cleared the custom orientation too. Assigned it to a button and am very happy. Thanks.

        scn = bpy.context.scene
        
        og_utdo = scn.tool_settings.use_transform_data_origin
        og_tot = scn.transform_orientation_slots[0].type

        scn.tool_settings.use_transform_data_origin = False
        scn.transform_orientation_slots[0].type = 'NORMAL'
        bpy.ops.transform.create_orientation(name='tmp', overwrite=True)
        scn.transform_orientation_slots[0].type = 'tmp'
        bpy.ops.object.mode_set(mode='OBJECT')
        scn.tool_settings.use_transform_data_origin = True
        bpy.ops.transform.transform(mode='ALIGN', orient_type='tmp', orient_matrix_type='tmp')
        scn.tool_settings.use_transform_data_origin = False
        bpy.ops.transform.delete_orientation()
        bpy.ops.object.rotation_clear()

        scn.tool_settings.use_transform_data_origin = og_utdo
        scn.transform_orientation_slots[0].type = og_tot