How can I efficiently duplicate a large amount of objects? 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 &quot;board&quot; 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:

Duplicate the active object and move it to the right (or down and to the left if the row ends
Rotates that active object
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={&quot;linked&quot;:True, &quot;mode&quot;:'TRANSLATION'}, TRANSFORM_OT_translate={&quot;value&quot;:(x, y, z), &quot;orient_type&quot;:'GLOBAL', &quot;orient_matrix&quot;:((1, 0, 0), (0, 1, 0), (0, 0, 1)), &quot;orient_matrix_type&quot;:'GLOBAL', &quot;constraint_axis&quot;:(True, False, False), &quot;mirror&quot;:True, &quot;use_proportional_edit&quot;:False, &quot;proportional_edit_falloff&quot;:'SMOOTH', &quot;proportional_size&quot;:1, &quot;use_proportional_connected&quot;:False, &quot;use_proportional_projected&quot;:False, &quot;snap&quot;:False, &quot;snap_target&quot;:'CLOSEST', &quot;snap_point&quot;:(0, 0, 0), &quot;snap_align&quot;:False, &quot;snap_normal&quot;:(0, 0, 0), &quot;gpencil_strokes&quot;:False, &quot;cursor_transform&quot;:False, &quot;texture_space&quot;:False, &quot;remove_on_cancel&quot;:False, &quot;release_confirm&quot;:False, &quot;use_accurate&quot;: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 &quot;Crash&quot;, 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?
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 &lt;= 2.7x
scene.objects.link(ob)

Link to collection in &gt;= 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 &gt; Object &gt; Instancing choose Verts.
To illustrate a script to duplivert a cone to a subsurfed suzanne.

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=&quot;Foo&quot;, 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