2

I'm trying to make a simple program where 15 spheres are randomly placed into a 20x20x20 volume, then after two seconds it clears all the spheres and places another 15 randomly.

It will place the first 15 spheres fine after two seconds, then after another two seconds blender closes.

Any help with this is much appreciated.

import bpy
import random
import threading

def place():

    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)

    for i in range(0,15):
        x = random.randint(-10,10)
        y = random.randint(-10,10)
        z = random.randint(-10,10)
        bpy.ops.mesh.primitive_uv_sphere_add(location=(x,y,z))

        bpy.ops.object.modifier_add(type='SUBSURF')
        bpy.context.object.modifiers['Subdivision'].render_levels=0
        bpy.context.object.modifiers['Subdivision'].levels=0

        timer = threading.Timer(2.0, place)
        timer.start()

    return None

place()
Ray Mairlot
  • 29,192
  • 11
  • 103
  • 125

2 Answers2

3

Threading and blender are not really designed to go together

Blender API Gotchas: threading

So far, no work has gone into making Blender’s Python integration thread safe, so until its properly supported, best not make use of this.

Here is the second example from link modified to. Add ten spheres, then randomly move them every 2 seconds with thread.

As mentioned in docs, will after a while kill blender.

Also will find that in threads (AFAIK timers are using threads too) context is lost Context is incorrect when calling from a timer . Recommend not referencing context from a thread / timer method. This includes use of operators.

In both timer examples below the spheres are found in the data by name..

import bpy

for i in range(10): bpy.ops.mesh.primitive_uv_sphere_add()

def func(): print("Running...") import bpy from random import randint spheres = [o for o in bpy.data.objects if o.name.startswith("Sphere")]

for o in spheres:
    o.location = tuple(randint(-10, 10) for a in "xyz")

def my_timer(): from threading import Timer t = Timer(2, my_timer) t.start() func()

my_timer()

This continues to run after blender is closed...

Using app.timers as suggested by @Gorgious

import bpy

for i in range(10): bpy.ops.mesh.primitive_uv_sphere_add()

def func(): print("Running...") import bpy from random import randint spheres = [o for o in bpy.data.objects if o.name.startswith("Sphere")]

for o in spheres:
    o.location = tuple(randint(-10, 10) for a in "xyz")

return 2

bpy.app.timers.register(func)

Use a modal timer operator.

Modal Timer Operator would be my recommendation for this.

Would recommend using a modal timer for this. See the example in Text Editor > Templates > Python > Operator modal timer change the timer tick/ or count ticks for 2 second intervals.

batFINGER
  • 84,216
  • 10
  • 108
  • 233
2

If I understand the code correctly, each place() method then recursively calls 15 more place() after 2 seconds, which each of these instances themselves call place() 15 more times after 2 seconds. etc. I think this will crash any program in python pretty quickly.

Blender has specialized methods for recurrent calling of methods. See the documentation : https://docs.blender.org/api/current/bpy.app.timers.html

Exemple :

import bpy


def every_2_seconds():
    print("Hello World")
    return 2.0


bpy.app.timers.register(every_2_seconds)
Gorgious
  • 30,723
  • 2
  • 44
  • 101
  • 1
    Think the recursive nature above was a logic error. The first 15 spheres not placed from thread call the next time it is, and crash. Even if unindented outside of for loop will In both cases ( using threading and using timer (using threading too i believe) } context will be lost. https://blender.stackexchange.com/questions/135970/context-is-incorrect-when-calling-from-a-timer Would recommend a modal timer operator to do this. – batFINGER Jun 05 '20 at 17:54