1
$\begingroup$

I have script (operator) which should align align all selected objects somewhat.

import bpy
from mathutils import Vector

class VCUTILS_OT_alignToLongest(bpy.types.Operator):
    bl_idname = "object.align_to_longest"
    bl_label = "Align Objects along their Longest Axis"
    bl_description = "Create average vector from the longest edges and align object to have x axis along this vector"
    bl_options = {'UNDO'}

    shortestEdgeEvaluated = 0.8

    def execute(self, context):
        for obj in bpy.context.selected_objects:
            if getattr(obj, 'type') in ['MESH']:
                self.alignObject(obj)

        return {'FINISHED'}

    def alignObject(self, obj):
        mesh = obj.data

        # Apply object rotation
        bpy.context.view_layer.objects.active = obj
        bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)

        # Find longest edge length
        longestD = 0.0
        for edge in mesh.edges:
            v1 = Vector(mesh.vertices[edge.vertices[0]].co)
            v2 = Vector(mesh.vertices[edge.vertices[1]].co)

            longestD = max(longestD, (v2 - v1).length)

        # Find direction as an average of longest edges
        axis = None
        for edge in mesh.edges:
            v1 = Vector(mesh.vertices[edge.vertices[0]].co)
            v2 = Vector(mesh.vertices[edge.vertices[1]].co)
            eVect = v2 - v1

            if eVect.length >= longestD * VCUTILS_OT_alignToLongest.shortestEdgeEvaluated:
                eVect.normalize()

                if axis is None:
                    axis = eVect
                else:
                    if axis.dot(eVect) > 0:
                        axis += eVect
                    else:
                        axis -= eVect

                    axis.normalize()

        # Align the object according to major axis found
        rotQuat = axis.to_track_quat('X', 'Z')
        rotEuler = rotQuat.to_euler()
#        rotEuler = ( 0, 0, 0.785 )
        print("Euler: " + str(rotEuler))
        obj.rotation_euler = rotEuler

        # Rotate the mesh back
        for v in mesh.vertices:
            v.co = Vector(v.co) @ rotQuat.to_matrix()

        print("Obj " + str(obj) + " done")

If I try it with that "proper rotation" it rotates the first selected object and ignores all others. It prints correct rotations for all objects to stdout, however. If I uncomment that line with "Rotation for debug" it rotates all selected objects to that fixed rotation.

I'am screwed now, I cannot imagine, where may be the problem ...

EDIT> Images before Before and after After. The leftmost selected is properly aligned. Other two selected not.

EDIT2> Added full script, python init and testfile

bl_info = {
    "name": "VCUtils",
    "author": "Vaclav Cermak <disnel@disnel.com>",
    "description": "Various my utilities",
    "version": (0, 9),
    "blender": (2, 80, 0),
    "location": "View3D",
    "warning": "",
    "category": "Generic"
    }

import bpy

from . alignToLongest_op import VCUTILS_OT_alignToLongest


classes = (
    VCUTILS_OT_alignToLongest,
)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
$\endgroup$
6
  • $\begingroup$ What is the aim here? To give all selected mesh objects the same global rotation? $\endgroup$
    – batFINGER
    Nov 25, 2019 at 13:27
  • $\begingroup$ No, the aim is to reorient all selected mesh objects to have their local x axis along their longest edges. Every object receives different rotation. $\endgroup$
    – VaclavC
    Nov 25, 2019 at 13:54
  • $\begingroup$ Please provide a before an after pic. Could align longest edge to global x by rotating at object level, but IMO would need to rotate mesh data locally (ie in edit mode) to align edge with local axis (and back again at object level if alignment is to be kept) Somewhat related blender.stackexchange.com/a/121227/15543 note the use of rotation difference. $\endgroup$
    – batFINGER
    Nov 25, 2019 at 14:35
  • $\begingroup$ Added to original question. $\endgroup$
    – VaclavC
    Nov 25, 2019 at 14:43
  • $\begingroup$ Without testing I'd say the issue is bpy.ops.object.apply... works on all selected objects, wheras you are calling it in a loop whilst changing active object, quite likely only the last set active object appears to "have worked" the others will be zeroed because of the op call. Kudos for putting the time into the script. However I would strongly recommend you please check out how I have done this in my answer. It is using no operators. Looping over hundreds of objects and calling operators blender will slow down considerably. $\endgroup$
    – batFINGER
    Nov 26, 2019 at 17:34

2 Answers 2

1
$\begingroup$

Transoform in place, align local $X$ axis to longest edge

the aim is to reorient all selected mesh objects to have their local x axis along their longest edges. Every object receives different rotation

  • Get all the meshes of selected objects, could be anything from 1 (all share same mesh) to having a mesh each.
  • Find the longest edge of each, rotate mesh about origin such that x axis aligns. Keep the inverse transform of this rotation.
  • For each selected object transform by inverse. (ie rotate back to orginal global orientation with new local axis)
  • Used rotation difference of two vectors. Using rotation difference of two tracking quats would help with aligning up axis too.
  • Assumes all meshes have at least one edge, used bmesh purely for BMEdge.calc_length() could use mesh data directly.

enter image description here

Example of use, LOCAL axis of three objects shown. Running script Rotates them in place such that the longest edge is aligned with the $X$ axis

Test script.

import bpy
import bmesh
from mathutils import Vector

context = bpy.context
ob = context.object
meshes = set(o.data for o in context.selected_objects if o.type == 'MESH')

bm = bmesh.new()
lut = {}
for me in meshes:
    bm.from_mesh(me)
    le = sorted(bm.edges, key=lambda e: e.calc_length()).pop()
    axis = (le.verts[1].co - le.verts[0].co).normalized()
    q = axis.rotation_difference(Vector((1, 0, 0)))
    R =  q.to_matrix()
    me.transform(R.to_4x4())
    lut[me] = R.transposed().to_4x4()
    bm.clear()
    
for o in (o for o in context.selected_objects if o.type == 'MESH'):
    o.matrix_world @= lut[o.data]
$\endgroup$
5
  • $\begingroup$ Thank you, but I don't have problem with transformation itself, it works. But there is something strange in Blender and it transforms only first from selected objects. Rotation for all others is calculated fine (I can see them in stdout), but Blender refuses to apply it to objects .... EDIT> When I uncomment that line "Rotation for debug", it sets that rotation for all three objects. But when I try to use the result from previous calculation, it sets rotation only for first object from selection, despite correct output to stdout $\endgroup$
    – VaclavC
    Nov 25, 2019 at 16:41
  • $\begingroup$ As mentioned answer is in response to desired result. To debug your code would need the full code, and perhaps a simple test blend to run it on. If the objects have another rotation type than euler then setting euler rotation does nada. Recommend working on matrices as suggested in answer. $\endgroup$
    – batFINGER
    Nov 26, 2019 at 4:20
  • $\begingroup$ Added full source and testfile to original question. If I uncomment that line with rotEuler = ( 0, 0, 0.785 ), it works and rotates all objects. But if I comment it out and try to set calculated rotation, it rotates only one selected object... $\endgroup$
    – VaclavC
    Nov 26, 2019 at 15:31
  • $\begingroup$ Upvoted, but I don't have enough reputation... Thank you! $\endgroup$
    – VaclavC
    Nov 27, 2019 at 13:25
  • $\begingroup$ Upvoted for you, I have enough reputation, fortunately. Please consider to accept the answer, thanks @VaclavC How this site works: blender.stackexchange.com/tour $\endgroup$
    – brockmann
    Sep 6, 2021 at 17:15
0
$\begingroup$

I did some modifications on the code above given by batfinger (because it was not working for me) (blender 2.93)

copy active object rotation considering the longest axis to selected (rotation mode does not matter and if rotations have been applied or not)

import bpy
import bmesh
from mathutils import Vector

context = bpy.context
ob = context.object
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
objs = set(o for o in context.selected_objects if o.type == 'MESH')

bm = bmesh.new()
mw = None
for ob in objs:
    me = ob.data
    bm.from_mesh(me)
    le = sorted(bm.edges, key=lambda e: e.calc_length()).pop()
    axis = (le.verts[1].co - le.verts[0].co).normalized()
    q = axis.rotation_difference(Vector((1, 0, 0)))
    R =  q.to_matrix()
    if ob == context.object:
        mw = R.to_4x4().inverted() 
    me.transform(R.to_4x4())
    bm.clear()

for o in (o for o in context.selected_objects if o.type == 'MESH'):
    o.matrix_world @= mw
$\endgroup$
0

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.