Complex scripting animation render

Hey everyone,

when you developed a script that builds geometry each frame which takes some time - how do you organize everything to render an animation out of it?

When I hit render animation, blender often crashes or weird things are going on. I think this is because building geometry takes longer than blender renderer reserves time between rendering each frame. I convert mesh data into bezier via ops and assign a material permanently by a frame change pre handler.

One solution was not to use frame pre handler but rendering each frame manually. I wait a second via time.wait(1) before I start rendering one frame manually. That worked well but I wondered if there is common workflow to force blender to wait until all the script is executed, geometry is built, materials are assigned etc:

myCustomFunctionBuildGeometry()
time.wait(1)
bpy.ops.render.render()

Maybe I can post an example if you don’t get me…

Would be cool to avoid time.wait because I never know how long it will take to convert into bezier etc - especially on different machines.

Greetings

Have you tried calling in from a pre render handler.\

http://www.blender.org/documentation/blender_python_api_2_62_1/bpy.app.handlers.html?highlight=handlers#bpy.app.handlers.render_pre

I just tried out, but I have the feeling that blender only makes sure that the script is executed but not that any of my converts via ops has finished too, what do you think?

My .blend:

script_test.blend (1.45 MB)

Hit ‘run script’ and then alt+a

Works at my machine as realtime preview, but when I hit render or use render pre handler, it mostly crashes or my material menues dissapear and blender doesn’t react properly anymore - especially when I render animation

(Win64, blender 2.63a)

I have done a lot of frame_change work and have not encountered a time limit. Your posted BLEND file keeps referencing a drive letter that I do not have on my system so Blender, annoyingly, keeps popping up a Try Again message box. However, I could see some of the code. The problem is your code is referencing the context in a render event. This is not allowed and produces an error when rendering. That is why your script does not work. Re-write your code to not use the context at all and it should work fine with rendering.

Excellent, that fixed everything!

Sorry, it didn’t fix it really. The problems came back even not using context.

For my tests I can pause the script one second before rendering but it will be hard to do with V-Ray Exporter later because I can’t use the regular render animation button then…

Maybe someone has any quick solution that works for my existing script and can reupload the blend?

script_test.blend (1.45 MB)

I hope file dependencies have now gone…

It’s not normal that whole menues disappear within the interface anyway? Do you experience this too with my file when you hit render animation and have a look at the material tab as an example?

Oh, there’s an error message besides:

Traceback (most recent call last):
File “C:\Users\Dennis\Downloads\blender_releases\blender-2.63a-release-windows64\2.63\scripts\addons\cycles\ui.py”, line 624, in poll
return context.material and CyclesButtonsPanel.poll(context)
AttributeError: ‘Context’ object has no attribute ‘material’

location:<unknown location>:-1

I think I could be right - there is too much happening in one frame for blender to handle it without waiting a moment before rendering, because I get no error messages if i do exactly this…

I see what you mean, Blender crashes. bpy.ops does not work in a Render event either (some can work but best to avoid and write your own versions). Also you don’t need to use bpy.data.scenes[0], you are provided with a scene by the event handler.

Have you read the Gotchas?
http://www.blender.org/documentation/blender_python_api_2_63_5/info_gotcha.html

My guess is you are operating upon stale data. Because we don’t really know what the bpy.ops do (unless you read the Blender source code), we can never be sure of what state variables are in after you execute those kinds of statements. bpy.ops should be used in an Operator context and thus are useful for modeling tools or one-shot generators.

Generally speaking, deleting and removing objects from memory should be avoided unless you are absolutely sure there are no other users of that datablock. Are you really setting the active object to the plane you just deleted?

I have commented some code out and this prevents the crash, but it may no longer function as you intended.


import bpy
import time

amountElements = 400

def my_handler(scene):
    modMesh = bpy.data.objects['Grid'].to_mesh(bpy.data.scenes[0],True,'RENDER')
    
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects['plasticplane.001'].select = True
    print("Skip delete for now.")
    #bpy.ops.object.delete()
    
    for i in range(0,amountElements):
        a = i+1
        print("Working on " + str(a))
        myTarget = bpy.data.objects['eq'+str(a)+'stange']
        vertexValue = modMesh.vertices[i].co.z*10+10
        
        eqTargetAdd = 0
        
        myTarget.scale.z = 5
        targetValue = (vertexValue/2)*(eqTargetAdd/2)+(vertexValue/2)+3
        myTarget.location.z = bpy.data.meshes['plasticplane'].vertices[i].co.z = targetValue
    
    print("Done with loop.")
    bpy.ops.object.select_all(action='DESELECT')
    print(".")
    bpy.data.objects['plasticplane'].select = True
    print(".")
    #bpy.ops.object.duplicate_move()
    print(".")
    bpy.data.scenes[0].objects.active = bpy.data.objects['plasticplane.001']
    print(".")
    bpy.ops.object.convert(target='CURVE')
    print(".")
    currentCurve = bpy.data.objects['plasticplane.001'].data
    print(".")
    currentCurve.bevel_object = bpy.data.objects['BezierCircle']
    print("Skip remove for now.")
    #bpy.data.meshes.remove(modMesh)
    print(".")
    bpy.data.objects['Grid'].location.z+=.008
    print(".")
    bpy.data.objects['plasticplane.001'].data.materials.append(bpy.data.materials['black'])
    print("Deselect")
    bpy.ops.object.select_all(action='DESELECT')


bpy.app.handlers.frame_change_pre.append(my_handler)

You are right Atom, I shouldn’t use ops in a frame loop

I couldn’t find a realtime modifier that creates a solid wireframe from a faceless mesh - maybe then I could avoid all these workarounds

What about something like this…

Looks interesting!

But not what I need I think



Cause I want to create a wireframe look - best script I’ve found so far is:
http://yorik.uncreated.net/guestblog.php?2011=73

But not anymore supported for 2.63 and also not working realtime…

Would be cool to have if someone of you is bored ^^

Have you tried adding a solidify modifier to what you already have?

Attachments


Okay - not to get misunderstood: What I already have is exactly what I want to have but it does cause some issues, because I use ops in a frame loop, so that isn’t ideal.

Here is what it does:

  1. take the faceless (only edges in it) base mesh and convert it into a curve
  2. take the new curve and apply a bevel object curve to it so the edges get some thickness

What I animate via python is the faceless mesh and each frame it should be converted to a solid wire to output something that has thickness. Okay I also could try to animate the already solid wire, but then it could get really complicated because the vertices don’t follow a simple grid pattern anymore.

So the only thing that would be useful is finding another method to get a solid wireframe autmatically. Solidify modifer doesn’t work at a faceless mesh as far as I know because it doesn’t create a wireframe.

But it’s not too important - I can work out a workaround I think

What about something like this…
After looking at the code I am not really sure why you need this to happen in a frame change? I am just guessing but all you really need is to create a bunch of slightly altered curves from a mesh.
This code does that.

It appears as a button in the Tools panel of the 3D window. Select the plasticplane and click the button and you will get a bunch of new curve objects that are like the mesh object. I may have borked the math a bit, but perhaps this can get you what you need.


import bpy

bl_info = {
    "name": "Convert",
    "author": "Atom",
    "version": (1, 0, 0),
    "blender": (2, 6, 3),
    "location": "View3D &gt; Tool Shelf",
    "description": "Convert plasticplane into curves.",
    'warning': '',
    "category": "Mesh"}
    
def removeMeshFromMemory (passedMeshName):
    # Extra test because this can crash Blender.
    mesh = bpy.data.meshes[passedMeshName]
    try:
        mesh.user_clear()
        can_continue = True
    except:
        can_continue = False
    
    if can_continue == True:
        try:
            bpy.data.meshes.remove(mesh)
            result = True
        except:
            result = False
    else:
        result = False
        
    return result

class MEshToCurves(bpy.types.Operator):
    bl_idname = 'object.mesh_to_curves'
    bl_label = 'MeshToCurves'
    bl_description = 'Mesh To Curves'
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):                
        ob = context.object
        if ob != None:
            source_name = "plasticplane"
            ob_source = bpy.data.objects.get(source_name)
            if ob_source != None:
                me_source = ob_source.data
                # Make a mesh for every element requested, derived from the source mesh.
                amountElements = 10
                
                # Loop and create.
                for i in range(0,amountElements):
                    a = i+1
                    new_name = 'new_'+str(a)+'_grid' 
                    me_temp = me_source.copy()          # Make a unique mesh from the original (memory gobble, gobble..)
                    me_temp.name = 'me_' + new_name
                    ob_temp = bpy.data.objects.new(new_name,me_temp)
                    context.scene.objects.link(ob_temp)
                    
                    vertexValue = me_temp.vertices[i].co.z*10+10
                    eqTargetAdd = 0
                    ob_temp.scale.z = 5
                    targetValue = (vertexValue/2)*(eqTargetAdd/2)+(vertexValue/2)+3
                    ob_temp.location.z = me_temp.vertices[i].co.z = targetValue
                
                # Loop and convert, apply bevel.
                for i in range(0,amountElements):
                    a = i+1
                    new_name = 'new_'+str(a)+'_grid' 
                    # Now that we have a bunch of objects, let's convert them to curves.
                    bpy.ops.object.select_all(action='DESELECT')
                    # No need to duplicate because we have already created everything in the previous loop.
                    #bpy.data.objects[new_name].select = True
                    #bpy.ops.object.duplicate_move()
                    bpy.data.objects[new_name].select = True                    # Select the object by it's name.
                    context.scene.objects.active = bpy.data.objects[new_name]
                    print("Converting [" + new_name + "] to a curve.")
                    bpy.ops.object.convert(target='CURVE')
                    currentCurve = context.active_object.data    #bpy.data.objects[new_name].data
                    print("New active object [" + context.active_object.name + "].")
                    print("New object type [" + context.active_object.type + "].")
                    ob_temp = context.active_object
                    currentCurve.name = 'cu_' + new_name
                    currentCurve.bevel_object = bpy.data.objects['BezierCircle']
                
                # Loop and memory cleanup.
                for i in range(0,amountElements):
                    a = i+1
                    ob_name = 'new_'+str(a)+'_grid' 
                    ob_temp = bpy.data.objects.get(ob_name)
                    if ob_temp != None:
                        #context.scene.objects.unlink(ob_temp)   # NOTE: Unlinked objects are still hanging around in memory.
                        me_name = 'me_new_'+str(a)+'_grid' 
                        # Attempt to remove mesh from memory, this can fail so review the result.
                        r = removeMeshFromMemory(me_name)
                        if r == True:
                            print ("Removed [" + me_name + "] from memory.")
                        else:
                            print ("Failed to remove [" + me_name + "] from memory.")
            else:
                print("Could not locate plasticplane.")
        return {'FINISHED'}

class ButtonMeshToCurves(bpy.types.Panel):
    bl_label = 'Mesh To Curves'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'

    def draw(self, context):
        layout = self.layout
        layout.operator('object.mesh_to_curves')
                         
bpy.utils.register_class(MEshToCurves)
bpy.utils.register_class(ButtonMeshToCurves)


Thanks! I will try this out tommorow