Updating Grease Pencil Data

Hello, I’m trying to edit GPencilPoints, however they aren’t updating unless I delete a keyframe or copy and paste the keyframes in a new GPencil object. This occurs after creating new keyframes with existing strokes (e.g. in Edit Mode). I have tried updating everything as I know to do it and I think it may be a bug. Can anyone please help if you know any better?

    active_layer = context.active_gpencil_layer
    frames = active_layer.frames
    start_frame = frames[0]

    for frame in range(start_frame.frame_number+1, context.scene.frame_end+1):
        new_frame = frames.copy(start_frame)
        for i,start_stroke in enumerate(start_frame.strokes):
            new_stroke = new_frame.strokes[i]
            for p,start_pt in enumerate(start_stroke.points):
                new_pt = new_stroke.points[p]
                new_pt.co[0] = start_pt.co[0]+(frame*0.5)
         new_frame.frame_number = frame

    gpencil = context.object
    gpencil.data.layers.update()
    for l in gpencil.data.layers:
        l.frames.update()
        for f in l.frames:
            f.strokes.update()
            for s in f.strokes:
                s.points.update()

    gpencil.data.update_tag()
    context.view_layer.depsgraph.update()
    context.view_layer.update()
    bpy.ops.gpencil.recalc_geometry()

You need to manually update the depsgraph, it sounds like. Tie all this code into a depsgraph handler:
https://docs.blender.org/api/current/bpy.app.handlers.html#bpy.app.handlers.depsgraph_update_post

Hi @josephhansen , thank you for the reply. Which code? Doesn’t this get called a lot? Also aren’t I updating the depsgraph here: context.view_layer.depsgraph.update()?

def depsgraph_callback(scene, depsgraph):
    #which code?

def register():
    bpy.app.handlers.depsgraph_update_post.append(depsgraph_callback)

Whatever code you want to run, I’m assuming everything you’ve written in your first post in the code block.

Yes, it gets called whenever data is changed. Isn’t that what you want?

I’ve never known this to do anything, I’m not sure what this function does but it definitely doesn’t trigger depsgraph handlers

@josephhansen This script should be run just once. Can I just call the depsgraph update once (like this)?

def depsgraph_callback(scene, depsgraph):
    #keyframe creation code
    bpy.app.handlers.depsgraph_update_post.remove(depsgraph_callback)

def register():
    bpy.app.handlers.depsgraph_update_post.append(depsgraph_callback)

I’d rather this be an Operator so I can call the command from the UI. I don’t think this callback method would allow for that with context, etc. since it’s only being called on register? Thanks!

Maybe in register try to append your handler and right away remove it ?

Or you could use a global flag that you test and set at first run for preventing a rerun^^

Am not sure of what you can call here or there as this is blender internal constrains but deserve to give it a try :slight_smile:

Happy blending !

Depsgraph always bothers me, I don’t know if it works as I have tried in many cases and I am not sure if I can fully trust it.

Unless I do something wrong someone please post the correct code.

Usually use hacks, to update another object/value higher than the one I am working on. And then let Blender handle the depsgraph update internally.

import bpy

def duplicate_strokes():
    context = bpy.context

    frame = context.scene.frame_current
    
    active_layer = context.object.data.layers.active
    frames = active_layer.frames

    start_frame = frames[0]

    new_frame = frames.copy(start_frame)
    
    for i,start_stroke in enumerate(start_frame.strokes):

        new_stroke = new_frame.strokes[i]
        
        for p,start_pt in enumerate(start_stroke.points):
            new_pt = new_stroke.points[p]
            new_pt.co[0] = start_pt.co[0]+(frame*0.5)
            new_frame.frame_number = frame
    
    def do_fake_depsgraph_update():
        context.scene.frame_current += 1
        context.scene.frame_current = frame
        
    def do_fake_depsgraph_update_with_operators():
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
        
    do_fake_depsgraph_update_with_operators()

duplicate_strokes()

Switching operators, might not work in some cases because say you are in an operator context (though perhaps you can use temp_override context). Another case is to change the timeline, usually it can update all drivers and animation data nicely.

Let me know if you have better ideas.

@const , I really appreciate the effort, but the hacks didn’t work for me. The only thing that gets the frames updated so far has been deleting or transforming a keyframe. This only works, however, after my operator has completed. I’ve tried changing context to replicate this, but it doesn’t work either:

    for area in context.screen.areas:
        if area.type == "DOPESHEET_EDITOR":
            bpy.ops.transform.transform(mode='TIME_TRANSLATE', value=(1, 0, 0, 0), orient_axis='Z', orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False, snap=False, snap_elements=set(), use_snap_project=False, snap_target='CLOSEST', use_snap_self=True, use_snap_edit=True, use_snap_nonedit=True, use_snap_selectable=False)
            bpy.ops.transform.transform(mode='TIME_TRANSLATE', value=(-1, 0, 0, 0), orient_axis='Z', orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False, snap=False, snap_elements=set(), use_snap_project=False, snap_target='CLOSEST', use_snap_self=True, use_snap_edit=True, use_snap_nonedit=True, use_snap_selectable=False)
            break

If your operator runs as modal it return {'RUNNING_MODAL'} it means that it will block all Blender events, not to allow other context-es to be updated.

See if you combine with return {'PASS_THROUGH'} every 10 loops, to see if you allow Blender to update other areas as well.

Generally pass through operators are cool because is like they run as background “threads”. However this causes your operator not to have exclusive and controlled functionality but combined with others as well, which causes logic errors.
(Say for example, you have some vertex processing going and you suddenly start to subdivide or triangulate the mesh or so, which is trouble…).