2
$\begingroup$

I have been looking for methods to create a set of objects from an Array Modifier. Normally one creates an Array, sets an Array Offset and manipulates it so that the transformation propagates to all the copies and most of the times the goal is to have a geometry as you don't care about the sub-parts own transforms but I do this time since my goal is to export these (empty in my case) transforms to a game engine.
To expand a bit, I already know that I can Apply the modifier and then separate the meshes By Loose Parts but it's not what I want and the reason for that is that with that method, such loose parts will inherit all the same rotation and scale from the original object; in fact the matrix of the single copy of the array is lost when applying the array.

My question is slightly different from this one: How can I use an array modifier to create individually manipulatable objects? in that I need the individual rotations and scales to be preserved.

What I need ideally is a workflow to go from an Array Modifier (but potentially parting ways from the Array Modifier) to create a big set (big enough to discourage manual duplication) of empty objects that keep their individual position, rotation and scale.

How can I achieve that?

$\endgroup$
1
  • 1
    $\begingroup$ Consider emulating the modifier settings in a panel or operator, which instead of modifying mesh duplicate and distribute. eg blender.stackexchange.com/a/121323/15543 btw the arrayed unit transform isn't lost when applying the array. All transforms could be calculated from original. $\endgroup$
    – batFINGER
    Commented Jul 20, 2020 at 17:11

1 Answer 1

6
$\begingroup$

Well since I couldn't find an exact solution for this anywhere, I came up with a Python script that processes all Array Modifiers of an object to create clones of such object (rather than just create geometry like Blender does) that correspond exactly to the distribution obtained with the Array Modifier.

This is particularly useful for me when I create something using Array Modifiers and want to export the whole geometry resulting from applying them but also the positions, rotations and scales of all parts. For example I use this in Unity to have one joined mesh to draw efficiently but using this script I can also keep the information about the parts' position, rotation and scale in Unity's Transforms and use this information for interactions.

import bpy
from mathutils import Vector, Matrix, Quaternion, Euler
from random import uniform

# Uses Array modifier data to generate OBJECTS rather than just geometry

# Only works with Array Modifiers with ObjectOffset
# Tested in Blender 2.83.1

# HOW TO USE
# - Select an object that has at least 1 Array Modifier
# - Make sure all Array Modifiers on that object have an Object Offset set and active
#  (those that aren't set or active will be skipped)
# - Optionally disable the Render/Realtime Display of each Array modifiers so you won't see the Blender's result
# - Launch this script with Alt+P

# - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Removes all modifiers from an object
## Needed to cleanup the clones
def RemoveModifiers(_obj):
    for mod in _obj.modifiers:
        _obj.modifiers.remove(mod)

# - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Recursively applies the affine transformation
# copying _obj and applying the transformation described by _matrix to its copies, relatively to the currently processed object matrix
# writes all cloned objects to _clonelist
## _OT is the original object transformation matrix
def CopyAffine(_context, _obj, _matrix, _count, _clonelist):
    copy = _obj.copy()
    RemoveModifiers(copy)
    _clonelist.append(copy)
    copy.matrix_world = _obj.matrix_world @ _matrix #@ _obj.matrix_world @ _matrix.inverted() # _matrix @ _obj.matrix_world # 
    _context.collection.objects.link(copy)
    c = _count - 1
    if (c > 1):
        CopyAffine(_context, copy, _matrix, c, _clonelist)

# - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Gets all array modifiers from _obj
def GetArrayModifiers(_obj):
    arrModifiers = []
    allModifiers = _obj.modifiers
    for mod in allModifiers:
        if(mod.name.startswith("Array")):
            arrModifiers.append(mod)
    return arrModifiers

# - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Process all Array Modifiers
def ApplyArrayModifiers(_context, _targetObject):
    arrModifiers = GetArrayModifiers(_targetObject)
    if(len(arrModifiers) == 0):
        print("No Array modifiers found in " + _targetObject.name)
    else:
        objs = []
        objs.append(_targetObject)
        arrModifiers.reverse() # we have to go backwards, from the last modifiers to the first
        for aMod in arrModifiers:
            count = aMod.count
            offsetObj = aMod.offset_object
            print(offsetObj.name)
            if(offsetObj == None or not aMod.use_object_offset):
                print("offsetObject not set or not active")
            else:
                clones = []
                # Here we need to counter the target object's transformation. Can this be seen as a change of base?
                D = _targetObject.matrix_world.inverted() @ offsetObj.matrix_world
                for obj in objs:
                    CopyAffine(context, obj, D, count, clones)
                objs = objs + clones # concatenate clones to all objects for the next iteration
                

######################################

print(" - - - - - - - - - - - - - - ")
context = bpy.context
A = context.view_layer.objects.active
ApplyArrayModifiers(context, A)

Blender Array Modifier

Blender Array Modifier (used as preview)

Result after running the script
All clones of the object are transformed in a way that matches the Array preview.

The result after running the script

Do mind that this is not perfect but I hope it helps.

$\endgroup$
8
  • 1
    $\begingroup$ Cheers for the edit. $\endgroup$
    – batFINGER
    Commented Jul 22, 2020 at 13:24
  • $\begingroup$ Not quite sure about the transform difference method above. Could use matrices to take rotation mode out of equation. eg ob.rotation_euler is not set for other types, instead could use ob.matrix_blah.to_euler() $\endgroup$
    – batFINGER
    Commented Jul 22, 2020 at 13:34
  • $\begingroup$ I’m quite sure it can be cleaned up to something more stilish. I don’t know the bpy API extensively and I seemed to have troubles with types. It’s a workaround. It works though $\endgroup$ Commented Jul 22, 2020 at 13:35
  • 1
    $\begingroup$ Probably you could make a plugin from it so no code implementing will be involved? For instance I'm not quite sure how to use it. Simple plugin would be great! $\endgroup$ Commented Dec 27, 2020 at 20:37
  • 1
    $\begingroup$ Hi @LiliaLilia there’s a short HOW TO USE section in the code, provided as comments. Not the best way to deliver but it’s there. Just paste it into Blender’s Text Editor and hit Alt+P. I will consider making a plug-in. Thanks for your suggestion $\endgroup$ Commented Dec 27, 2020 at 22:55

You must log in to answer this question.

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