0
$\begingroup$

Hey all so 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 is 17,000 objects big and blender crashes when ever I run the script.. it worked great when I was only doing 30/40 objects in testing but 17k is just too much

script does this:

  1. duplicates active object and moves to the right (or down and to the left if its the end of the row)

  2. rotates that object

  3. repeat!

I'm fairly new to blender, a few donut and modeling tutorials in. Should I be going about this another way?

I tried to run the script on the starter cube and that crashed too so its not poly count

also, I tried time.sleep(0.1) to try and slow it down or something it 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: It doesn't "Crash" it just hangs and is non-responsive.. currently just letting it sit but its been 10 minutes haha. So a better question might be how do I speed this up or have a way where I can let it run and can see the incremental changes so I know its working.

$\endgroup$
  • $\begingroup$ Could you add your script to your post or at least the looping part ? $\endgroup$ – Gorgious Jan 14 at 20:10
  • $\begingroup$ @NathanHild posted $\endgroup$ – DrewInSpace Jan 14 at 20:27
  • $\begingroup$ Maybe redo your script to work with duplifaces, since you have duplicates? $\endgroup$ – Serge L Jan 14 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 at 22:36
  • $\begingroup$ blender.stackexchange.com/questions/7358/… $\endgroup$ – batFINGER Jan 15 at 0:38
1
$\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()

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

monkey.instance_type = 'VERTS'
monkey.use_instance_vertices_rotation = True
| improve this answer | |
$\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.