1
$\begingroup$

I wrote a blender script that duplicates an object, rotates it, and eventually creates a mosaic, data looks like this: [x,x,x, o,o,o, x,x,x,], where x is 90 degrees and o is 180, for example. Creating a "board" of the rotated objects.

The problem is that my mosaic consists of 17,000 objects and blender crashes when ever I run the script. It worked great when I was only creating 30/40 objects in my first tests but 17k is just too much to handle that. Routine:

  1. Duplicate the active object and move it to the right (or down and to the left if the row ends
  2. Rotates that active object
  3. Repeat both steps

I'm fairly new to blender, is there a better way to achieve that? I tried to run the script on the default cube and that crashed too. Also, I tried adding time.sleep(0.1) to slow it down or something but that also crashes:

import bpy.ops
from math import radians
from mathutils import Matrix
import time

def move_obj(x, y, z):
   bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(x, y, z), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(True, False, False), "mirror":True, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False})

def rotate_object(rot_mat):
    obj = bpy.context.active_object
    orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose()
    
    orig_loc_mat = Matrix.Translation(orig_loc)
    orig_rot_mat = orig_rot.to_matrix().to_4x4()
    orig_scale_mat = (Matrix.Scale(orig_scale[0],4,(1,0,0)) @
                      Matrix.Scale(orig_scale[1],4,(0,1,0)) @
                      Matrix.Scale(orig_scale[2],4,(0,0,1)))
                      
    obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat
    
def rotate_dice(num):
    if num == 2: rotate_object( Matrix.Rotation(radians(90), 4, 'X')) #2
    if num == 3: rotate_object( Matrix.Rotation(radians(270), 4, 'Z')) #3
    if num == 4: rotate_object( Matrix.Rotation(radians(270), 4, 'X')) #4
    if num == 5: rotate_object( Matrix.Rotation(radians(90), 4, 'Z')) #5
    if num == 6: rotate_object( Matrix.Rotation(radians(180), 4, 'X')) #6

def rotate_to_one(num): 
    if num == 2: rotate_object( Matrix.Rotation(radians(-90), 4, 'X')) #2
    if num == 3: rotate_object( Matrix.Rotation(radians(-270), 4, 'Z')) #3
    if num == 4: rotate_object( Matrix.Rotation(radians(-270), 4, 'X')) #4
    if num == 5: rotate_object( Matrix.Rotation(radians(-90), 4, 'Z')) #5
    if num == 6: rotate_object( Matrix.Rotation(radians(-180), 4, 'X')) #6

def dup(to, fro, x, z):
    move_obj(x, 0, z)
    rotate_to_one(fro)
    rotate_dice(to)

dice_arr = [1,2,3,4,5,1,2,3,4,5] // actually 17,000 entries long (removed for post)
break_on = 119

def run_arr():
    counter = 0
    last_index = 1
    movement_amount = 2.075
    count = 0
        
    for i in dice_arr: 
        if count == break_on: 
            dup(i, last_index, -movement_amount * break_on, -movement_amount) 
            count = 0
        else: 
            count = count + 1
            dup(i, last_index, movement_amount, 0)
            
        last_index = i
        time.sleep(0.1)

run_arr()


Edit: Blender doesn't seem to "Crash", it just hangs and is not responsive. How do I speed this up or let it run and so I can see the incremental changes?

$\endgroup$
5
  • $\begingroup$ Could you add your script to your post or at least the looping part ? $\endgroup$
    – Gorgious
    Jan 14 '20 at 20:10
  • $\begingroup$ @NathanHild posted $\endgroup$ Jan 14 '20 at 20:27
  • $\begingroup$ Maybe redo your script to work with duplifaces, since you have duplicates? $\endgroup$
    – Serge L
    Jan 14 '20 at 22:19
  • $\begingroup$ Did you time the execution on say your 30 test objects ? Because if it takes 1 second or more, the execution will definitely take more than 10 minutes... Also you could look into optimizing your code, with if/else statements instead of multiple if statements. And it looks like you are only doing the same 5 sets of operations multiple times, which could be optimized by storing the 5 dice then only duplicating them and moving them later. And remove the time.sleep(0.1), it effectively adds around half an hour of computation with 17000 loops. Food for thought $\endgroup$
    – Gorgious
    Jan 14 '20 at 22:36
  • $\begingroup$ blender.stackexchange.com/questions/7358/… $\endgroup$
    – batFINGER
    Jan 15 '20 at 0:38
4
$\begingroup$

Remove repeated operator calls

Most likely this is a veiled duplicate of

Python performance with Blender operators

Removing 17,000 operator calls will help... ie removing bpy.ops.object.duplicate_move using low level API calls

# copy the object
copy = ob.copy()
# copy and link data, prob not needed here
mesh_copy = copy.data.copy()
copy.data = mesh_copy
# set the local location
copy.location = (0, 0, 0)
# and / or set the global translation
copy.matrix_world.translation = (0, 0, 0) # scene origin

Link to scene in <= 2.7x

scene.objects.link(ob)

Link to collection in >= 2.80

context.collection.objects.link(ob)

In 2.8 for example move_obj can become, (Note there is no need to link them to collection here, could do it later)

def dupe_move_ob(coll, ob, vec):
    copy = ob.copy()
    copy.matrix_world = Matrix.Translation(vec) @ copy.matrix_world
    coll.objects.link(copy)
    return copy

Returns a reference to the newly created and moved object.

Similarly would change other methods to pass objects

def rotate_object(ob, rot_mat):

Dupli

For 17, 000 objects will still be slowish. As commented consider using some form of dupli for this, eg dupliverts, make one mesh, add verts at all the locations calculated, set their normals (via the rotation) and one object will be duplicated for each vertex aligned to the vertex normal.

Make the new mesh object the parent of dupli object and under Properties > Object > Instancing choose Verts.

To illustrate a script to duplivert a cone to a subsurfed suzanne.

enter image description here

The vertex normals of suzanne are determined by the faces. If we construct a vert only mesh, then set v.normal = (x, y, z) for each vertex

import bpy
from math import radians
from mathutils import Matrix

context = bpy.context

bpy.ops.mesh.primitive_monkey_add()
monkey = context.object
monkey.modifiers.new(name="Foo", type='SUBSURF')

bpy.ops.mesh.primitive_cone_add()
cone = context.object
cone.scale *= 0.1
cone.parent = monkey
cone.data.transform(Matrix.Translation((0, 0, 1)))
cone.data.transform(Matrix.Rotation(radians(-90), 4, 'X'))

monkey.instance_type = 'VERTS'
monkey.use_instance_vertices_rotation = True
$\endgroup$

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.