Using Threaded Timer To Change Mesh.

Hi All,

I was playing around with the twisted torus addon and grabbed the mesh generation code.

What I want to do is to create an object and link it to the mesh generated from the twisted torus code. That part works.

Then I start a timer and 5 seconds later it goes off. When that even happens I generate another twisted torus mesh, with different parameters. I want to link that newly created mesh to the existing object so it changes the shape to the new mesh. I would also like to do memory cleanup on the old discarded mesh so it does not reside in memory any more.

I am new to threading and I don’t understand why the code is not working when the timer fires.

If anyone has any time to look at it, that would be great.


import bpy

import mathutils
from mathutils import *
from math import cos, sin, pi

import time
import threading

class Timer(threading.Thread):
    def __init__(self, seconds):
        self.runTime = seconds
        threading.Thread.__init__(self)

    def run(self):
        time.sleep(self.runTime)
        
        # I execute after this thread counts down.
        ob = fetchIfObject("myTwistedTorus")
        if ob != None:
            print(ob.name)
            print(ob.data)
            updateTwistedTorus(ob,"myTwistedTorus",4.0,0.42,16,16,5)
            print ("Updated torus mesh.")
        else:
            print ("Got None for object...")

############################################################################
# Torus mesh code generation thanks to Paulo Gomes!
# add_mesh_twisted_torus.py Copyright (C) 2009-2010, Paulo Gomes
# tuga3d {at} gmail {dot} com
############################################################################
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
    faces = []

    if not vertIdx1 or not vertIdx2:
        return None

    if len(vertIdx1) < 2 and len(vertIdx2) < 2:
        return None

    fan = False
    if (len(vertIdx1) != len(vertIdx2)):
        if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
            fan = True
        else:
            return None

    total = len(vertIdx2)

    if closed:
        # Bridge the start with the end.
        if flipped:
            face = [
                vertIdx1[0],
                vertIdx2[0],
                vertIdx2[total - 1]]
            if not fan:
                face.append(vertIdx1[total - 1])
            faces.append(face)

        else:
            face = [vertIdx2[0], vertIdx1[0]]
            if not fan:
                face.append(vertIdx1[total - 1])
            face.append(vertIdx2[total - 1])
            faces.append(face)

    # Bridge the rest of the faces.
    for num in range(total - 1):
        if flipped:
            if fan:
                face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
            else:
                face = [vertIdx2[num], vertIdx1[num],
                    vertIdx1[num + 1], vertIdx2[num + 1]]
            faces.append(face)
        else:
            if fan:
                face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
            else:
                face = [vertIdx1[num], vertIdx2[num],
                    vertIdx2[num + 1], vertIdx1[num + 1]]
            faces.append(face)

    return faces


def calculate_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
    PI_2 = pi * 2.0
    z_axis = (0.0, 0.0, 1.0)

    verts = []
    faces = []

    edgeloop_prev = []
    for major_index in range(major_seg):
        quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
        rot_twists = PI_2 * major_index / major_seg * twists

        edgeloop = []

        # Create section ring
        for minor_index in range(minor_seg):
            angle = (PI_2 * minor_index / minor_seg) + rot_twists

            vec = Vector((
                major_rad + (cos(angle) * minor_rad),
                0.0,
                sin(angle) * minor_rad))
            vec = vec * quat

            edgeloop.append(len(verts))
            verts.append(vec)

        # Remember very first edgeloop.
        if major_index == 0:
            edgeloop_first = edgeloop

        # Bridge last with current ring
        if edgeloop_prev:
            f = createFaces(edgeloop_prev, edgeloop, closed=True)
            faces.extend(f)

        edgeloop_prev = edgeloop

    # Bridge first and last ring
    f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
    faces.extend(f)

    return verts, faces

############################################################################
# Code for returning objects, groups or meshes.
############################################################################
def fetchIfObject (passedName= ""):
    try:
        result = bpy.data.objects[passedName]
    except:
        result = None
    return result

def generateTwistedTorus(passedName,r1,r2,s1,s2,t):
    edges = []
    verts, faces = calculate_twisted_torus(r1,r2,s1,s2,t)
    # Create new mesh
    mesh = bpy.data.meshes.new("me_" + passedName)

    # Make a mesh from a list of verts/edges/faces.
    mesh.from_pydata(verts, edges, faces)

    # Update mesh geometry after adding stuff.
    mesh.update()
    ob = bpy.data.objects.new(passedName, mesh)
    bpy.context.scene.objects.link(ob)

def updateTwistedTorus(passedObject,passedName,r1,r2,s1,s2,t):
    edges = []
    verts, faces = calculate_twisted_torus(r1,r2,s1,s2,t)
    # Create new mesh
    new_mesh = bpy.data.meshes.new("me_" + passedName)

    # Make a mesh from a list of verts/edges/faces.
    new_mesh.from_pydata(verts, edges, faces)

    # Update mesh geometry after adding stuff.
    new_mesh.update()
    old_mesh = passedObject.data
    print("removing old_mesh", old_mesh)
    bpy.data.meshes.unlink(old_mesh)
    print("adding new mesh")
    passedObject.link(new_mesh)

# Make the initial twisted torus.
generateTwistedTorus("myTwistedTorus",2.0,0.25,32,32,5)

# In 5 seconds change the mesh of the twisted torus.
t = Timer(5)
t.start()

Might pay to hold off till python 3.2 is with us. (which is soon i believe) . was reading up on something else … but i think i noticed something about threading support being much better… not to mention error handling.

unlink is not for mesh


old_mesh.user_clear()
if (old_mesh.users == 0):
        bpy.data.meshes.remove(old_mesh)
        #for the last builds:
        #bpy.types.BlendDataMeshes.remove()

@orinoco56: Thank you, that worked.

Here is the revised code that generates a torus, then 5 seconds later changes it to a new shape.
Blender 2.56 r35129


import bpy

import mathutils
from mathutils import *
from math import cos, sin, pi

import time
import threading

class Timer(threading.Thread):
    def __init__(self, seconds):
        self.runTime = seconds
        threading.Thread.__init__(self)

    def run(self):
        time.sleep(self.runTime)
        
        # I execute after this thread counts down.
        ob = fetchIfObject("myTwistedTorus")
        if ob != None:
            updateTwistedTorus(ob,"myTwistedTorus",4.0,0.42,16,16,5)
            print ("Updated torus mesh.")
        else:
            print ("Got None for object...")

############################################################################
# Torus mesh code generation thanks to Paulo Gomes!
# add_mesh_twisted_torus.py Copyright (C) 2009-2010, Paulo Gomes
# tuga3d {at} gmail {dot} com
############################################################################
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
    faces = []

    if not vertIdx1 or not vertIdx2:
        return None

    if len(vertIdx1) < 2 and len(vertIdx2) < 2:
        return None

    fan = False
    if (len(vertIdx1) != len(vertIdx2)):
        if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
            fan = True
        else:
            return None

    total = len(vertIdx2)

    if closed:
        # Bridge the start with the end.
        if flipped:
            face = [
                vertIdx1[0],
                vertIdx2[0],
                vertIdx2[total - 1]]
            if not fan:
                face.append(vertIdx1[total - 1])
            faces.append(face)

        else:
            face = [vertIdx2[0], vertIdx1[0]]
            if not fan:
                face.append(vertIdx1[total - 1])
            face.append(vertIdx2[total - 1])
            faces.append(face)

    # Bridge the rest of the faces.
    for num in range(total - 1):
        if flipped:
            if fan:
                face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
            else:
                face = [vertIdx2[num], vertIdx1[num],
                    vertIdx1[num + 1], vertIdx2[num + 1]]
            faces.append(face)
        else:
            if fan:
                face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
            else:
                face = [vertIdx1[num], vertIdx2[num],
                    vertIdx2[num + 1], vertIdx1[num + 1]]
            faces.append(face)

    return faces


def calculate_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
    PI_2 = pi * 2.0
    z_axis = (0.0, 0.0, 1.0)

    verts = []
    faces = []

    edgeloop_prev = []
    for major_index in range(major_seg):
        quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
        rot_twists = PI_2 * major_index / major_seg * twists

        edgeloop = []

        # Create section ring
        for minor_index in range(minor_seg):
            angle = (PI_2 * minor_index / minor_seg) + rot_twists

            vec = Vector((
                major_rad + (cos(angle) * minor_rad),
                0.0,
                sin(angle) * minor_rad))
            vec = vec * quat

            edgeloop.append(len(verts))
            verts.append(vec)

        # Remember very first edgeloop.
        if major_index == 0:
            edgeloop_first = edgeloop

        # Bridge last with current ring
        if edgeloop_prev:
            f = createFaces(edgeloop_prev, edgeloop, closed=True)
            faces.extend(f)

        edgeloop_prev = edgeloop

    # Bridge first and last ring
    f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
    faces.extend(f)

    return verts, faces

############################################################################
# Code for returning objects, groups or meshes.
############################################################################
def fetchIfObject (passedName= ""):
    try:
        result = bpy.data.objects[passedName]
    except:
        result = None
    return result

def generateTwistedTorus(passedName,r1,r2,s1,s2,t):
    edges = []
    verts, faces = calculate_twisted_torus(r1,r2,s1,s2,t)
    # Create new mesh
    mesh = bpy.data.meshes.new("me_" + passedName)

    # Make a mesh from a list of verts/edges/faces.
    mesh.from_pydata(verts, edges, faces)

    # Update mesh geometry after adding stuff.
    mesh.update()
    ob = bpy.data.objects.new(passedName, mesh)
    bpy.context.scene.objects.link(ob)

def updateTwistedTorus(passedObject,passedName,r1,r2,s1,s2,t):
    edges = []
    
    # Generate a new mesh to re-link to this passed object.
    verts, faces = calculate_twisted_torus(r1,r2,s1,s2,t)
    new_mesh = bpy.data.meshes.new("me_" + passedName)  # Create new mesh
    new_mesh.from_pydata(verts, edges, faces)           # Make a mesh from a list of verts/edges/faces.
    new_mesh.update()                                   # Update mesh geometry after adding stuff.
    
    old_mesh = passedObject.data                        # Fetch the old mesh that is currently linked to our object.
    
    print("adding new mesh")
    passedObject.data = new_mesh

    print("removing old_mesh")
    # orinoco56 code.
    old_mesh.user_clear()
    if (old_mesh.users == 0):
            bpy.data.meshes.remove(old_mesh)
            #for the last builds:
            #bpy.types.BlendDataMeshes.remove()
    else:
        print ("Unable to clear users, linked to another object perhaps...?")


# Make the initial twisted torus.
generateTwistedTorus("myTwistedTorus",2.0,0.25,32,32,5)

# In 5 seconds change the mesh of the twisted torus.
t = Timer(5)
t.start()

@atom

i have been trying to make the timer code work. but, in the following, both cubes appear simultaneously - no delay

what have i missed out??

import bpy
import time
from threading import Thread

class timer(Thread):
    def __init__(self):
        Thread.__init__(self)

    def run(self):
        time.sleep(5)

def task(n):
  bpy.ops.mesh.primitive_cube_add(location=(0,0,n))  
  t = timer()
  t.start()
  bpy.ops.mesh.primitive_cube_add(location=(0,0,n+3))
  
task(0)

Remember, the delay happens when the run fires. There is no implied delay just because you issued a .start(). The line right after it gets executed immediately in top down code fashion. Also, I am not sure how to pass a value to a timer either. So you may have to use globals in that situation.


import bpy
import time
from threading import Thread

class timer(Thread):
    def __init__(self):
        Thread.__init__(self)

    def run(self):
        time.sleep(5)
        #The timer has expired, time to run our code.
        bpy.data.objects["Cube"].location = (0,0,3)

def task(n):
    bpy.ops.mesh.primitive_cube_add(location=(0,0,n))  
    t = timer()
    t.start()
    #Now that the timer is started we wait for the event to fire and execute our next line code.
  
task(0)

There are also problems using bpy.ops in threads. The context can be invalid. So it is best to operate directly on the data instead.

You may want to check out the ModalTimerOperator example script available from the menu of a text window. The modal timer does give you a valid context.

great, thanks, i get that now.

but…after the second line of code has been executed (ie cube has been moved) i can’t figure out to which point control is transferred…
i haven’t got my head around threading - it doesn’t seem to work like calling a subroutine say, and then control returns to the place from which it was called…

what i am really trying to do is simply delay execution of a function for some milliseconds then continue

Well, you don’t need a thread to delay, just use time.


import bpy
import time

def one():
    print("one")

def two():
    print("two")

one()
time.sleep(5)
two()

The problem with this is that you are running python code in a single thread and holding up Blender’s ability to do anything else. So if your def one function is updating the scene, you will not see it until after def two completes. This would be a reason to use threads.

Something like this can be used as a framework for deferred execution. But in this situation, you will not have access to bpy.ops or bpy.context and must operate on the bpy.data directly.


import bpy
import time
import threading

def myThreadReview(lock):
    # When the thread is started, setup our local defaults.
    isBusy = False
    shouldLoop = True
    
    myCount = 0
   
    while shouldLoop:
        # We have entered an infinite loop that we may never exit from.
        if isBusy:
            # Skip any further processing, we are already busy.
            pass
        else:
            isBusy = True
            # Put your code HERE.
            if myCount < 5:
                # We only want to do this 5 times.
                bpy.data.objects["Cube"].location = (0,0,myCount * 2.5)
                myCount = myCount + 1
                time.sleep(2)
            else:
                # just pass or exit loop, do not restart timer.
                #shouldLoop = False
                pass
            isBusy = False
    return

# Start our infinite loop thread, after this you must close Blender to exit this code.
run_locked = True
if run_locked:
    lock = threading.Lock()
    lock_holder = threading.Thread(target=myThreadReview, args=(lock,), name='MyThreadNameLocked')
    lock_holder.setDaemon(True)
    lock_holder.start()
else:
    lock = None
    t = threading.Thread(target=myThreadReview,args=(lock,), name='MyThreadName')
    t.start()

sorry if I missing something here python allows for threads but blender python does not as far as I have heard, something changed ?

seems like threading is available but won’t always work as expected

@atom, thanks for your suggestion, but it all seems mighty complicated for a simple task:-)

i see what you mean about sleep holding up single-threaded blender processes. for example, this simple delay works:

def timer():
    delay.sleep(2)
    return
    
def one():
    print("one")
    delay()
    print("two")
    
one()  

but this one doesn’t because the update process is also delayed!!

bpy.ops.mesh.primitive_cube_add()
bpy.ops.mesh.primitive_cube_add()

def delay():
    time.sleep(2)
    return

def addob(n):
    bpy.data.objects["Cube"].location = (0,0,n)
    delay()
    bpy.data.objects["Cube"].location = (0,0,n+2)
      
addob(3)

maybe i will have to use the old delay loop trick …

@steeve: Interesting discovery. Just wondering how you are going to render it, however?

@kilon: You can use standard python threads, but you then face the possibility of being out of synch with BPY. What this means is you have to error protect every access to BPY to avoid crashing your thread. Typically reads are almost always ok, but when you write to bpy.data it may not work every time.

I did a short video tutorial on the subject here:
http://vimeo.com/21276947

Oh thank you for that, I am checking now your tutorial. Does that mean that using multiprocessing module is possible too ?

http://docs.python.org/library/multiprocessing.html

multiprocessing has the advantage of being able to use the processing power of multiple cores something normal python threads cannot do because of python GIL.

@steeve: Interesting discovery. Just wondering how you are going to render it, however?

i haven’t thought it through that far yet …