3
$\begingroup$

There's a script I find very useful that works in 2.7x that is no longer working in 2.8x, 2.9x. I want to update it to work with current Blender versions. The script takes an object and makes a duplicate of it where it would be at each frame, applying mesh deformations. It seems it stopped working because of these Python API changes that came with the new Collections system.

The script that works in 2.7x was authored by CodeManX:

import bpy

scene = bpy.context.scene
frame_current = scene.frame_current

r = range(scene.frame_start, scene.frame_end + 1)
l = len(str(len(r)))
obs = []

for f in r:
    scene.frame_set(f)

    for ob in scene.objects:
        if ob.type == 'MESH' and ob.name.startswith("model"):
            me = ob.to_mesh(scene, True, 'PREVIEW')
            new_ob = bpy.data.objects.new("%s_frame%0*i" % (ob.name, l, f), me)
            new_ob.matrix_world = ob.matrix_world.copy()
            obs.append(new_ob)

scene.frame_set(frame_current)
for ob in obs:
    scene.objects.link(ob)
scene.update()

I messaged the author of the original code last week, but it appears he is not so active on Blender SE these days, so I'm not holding my breath for a response. Besides, maybe in the process of trying to fix this I can learn more about how the API works.

According to the API changes, I replaced scene = bpy.context.scene with layer = bpy.context.view_layer and that resolved one error. But now I am trying to resolve this one:

AttributeError: 'ViewLayer' object has no attribute 'frame_current'

This page of the Blender Manual still references scene.frame_current. Is this manual page outdated? Or am I misunderstanding and we can still use scene in certain cases?

This is my work-in-progress modified script, partially fixed for 2.8+:

import bpy

layer = bpy.context.view_layer
frame_current = layer.frame_current

r = range(layer.frame_start, layer.frame_end + 1)
l = len(str(len(r)))
obs = []

for f in r:
    layer.frame_set(f)

    for ob in layer.objects:
        if ob.type == 'MESH' and ob.name.startswith("model"):
            me = ob.to_mesh(layer, True, 'PREVIEW')
            new_ob = bpy.data.objects.new("%s_frame%0*i" % (ob.name, l, f), me)
            new_ob.matrix_world = ob.matrix_world.copy()
            obs.append(new_ob)

layer.frame_set(frame_current)
for ob in obs:
    layer.objects.link(ob)
layer.update()

Any help would be greatly appreciated. And if it can explained in a general way that may apply to others having problems updating a 2.7 script, or specifically those wondering about how frame_current has changed, that would be great.

Finally, one possible hint of new syntax is in this answer where the author has crossed out frame_current = 1 and replaced it with frame_set(1) ...unfortunately my logic of trying frame_get() did not work.

$\endgroup$

1 Answer 1

4
$\begingroup$

Updated script.

All changes here have been well documented.

Getting the updated mesh has changed.

How do I get a mesh data-block with modifiers and shape keys applied in Blender 2.8?

Scene update has been replaced.

What is the replacement for scene.update()?

Setting the frame is still done the same. (frame_set is not a member of ViewLayer.)

Linking object to a scene has changed.

link new object to scene with python in 2.8

View layer objects has no link method.

revised script.

import bpy
context = bpy.context
scene = context.scene
dg = context.evaluated_depsgraph_get()

frame_current = scene.frame_current

r = range(scene.frame_start, scene.frame_end + 1)
l = len(str(len(r)))
obs = []

for f in r:
    scene.frame_set(f)
    #dg = context.evaluated_depsgraph_get()
    for ob in scene.objects:
        if ob.type == 'MESH' and ob.name.startswith("Cube"):
            ev_ob = ob.evaluated_get(dg)
            me = ev_ob.data.copy()
            new_ob = bpy.data.objects.new("%s_frame%0*i" % (ob.name, l, f), me)
            new_ob.matrix_world = ob.matrix_world.copy()
            obs.append(new_ob)

scene.frame_set(frame_current)
for ob in obs:
    context.collection.objects.link(ob)
context.view_layer.update()

Note haven't tested this thoroughly, re how often to evaluate the depsgraph, uncomment #dg = context.evalua.... to see if result differs when it is evaluated on a per frame basis.

$\endgroup$
8
  • 2
    $\begingroup$ Tested the script and it is working as expected. I'm wondering what is the purpose of the last line in the script ? It seems if I comment it out, it behaves exactly the same. Also, why can't we link the objects to the context collection inside the main for loop ? Blender hangs when I try to... $\endgroup$
    – Gorgious
    Commented Oct 13, 2020 at 8:54
  • 1
    $\begingroup$ @Gorgious To emulate the old scene update. Which as pointed out as last line of script, or for this one is most likely not required. Often an update is not required in a script, can get in the habit of sticking one in, just in case. Changing object matrices in a loop then looking at value later would often fail without an update... similarly with adding modifiers. (A recent post looking at mesh count before and after adding a modifier using method above required a new depsgraph either side) $\endgroup$
    – batFINGER
    Commented Oct 13, 2020 at 9:02
  • $\begingroup$ Thank you batFINGER, you genius Python ninja! thanks for providing those links. I will have to study up to try to understand the why of it. Can you please elaborate a bit re your note about the depsgraph? Because it looks to me like it is getting evaluated every frame, and I don't see a difference when that line is uncommented. $\endgroup$
    – Mentalist
    Commented Oct 13, 2020 at 9:07
  • 1
    $\begingroup$ Put in as an after-thought. Relates to this blender.stackexchange.com/questions/196990/… where it was required for getting the evaluated mesh before and after adding another modifier. Because of all the weird and wonderful ways blenderers animate thought would add. In my simple tests with animating modifier props with #frame driver it worked as expected without too. @Gorgious re linking in loop adds objects to loop ... prob making an infinite loop. for ob in scene.objects[:]: $\endgroup$
    – batFINGER
    Commented Oct 13, 2020 at 9:36
  • 1
    $\begingroup$ Oh Ok. What about if you make a new unlinked to scene collection, link objs in loop and then lastly link collection to scene. Twiddled around adding that since it is a bonus (IMO) to be able to deal with N x frames objects in one fell swoop without them being splurted into the scene. Which can do by remembering to make an approp collection context before running script. $\endgroup$
    – batFINGER
    Commented Oct 13, 2020 at 9:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .