Motion-Streaks/Speed-Lines using Geometry Nodes Feedback trick

feedback Curve.blend (105.1 KB)

Old mesh line method

feedback.blend (107.3 KB)

Thanks to the feedback “hack” (first made aware of it here) I’m showcasing a technique to create Motion-Streaks/Speed-Lines on existing geometry undergoing some animation.

Feedback network:

The solver network:

Basically what is happening is that curve lines with as many points as there are frames in an animation are instanced on the geometry. Object indices are captured for the initial state. On the evolving animation, the position of the curve lines are set for each frame.

Go to frame 1. Make a copy of the solver object and run the animation. Don’t loop the animation, make it run almost to the end, then manually press the right arrow button to complete the animation… Then apply the Geometry Nodes modifier and you’ll have the motion paths of the “Trail Object”.

Edit: Update to file to use Curve Line instead of Mesh Line for easier-to-read network. Had to add an intermediary object to convert to mesh:
… the warning should be ignored since applying the modifier won’t work without it.



Could not resists and spend some time on a little script to render those feedback hacks with cycles. Mostly following your proposal from the higgsas thread:

Here is the file with the script:
feedback_cycles_01.blend (129.3 KB)

Run script to register operator and add button.
To start press the new operator button in the modifier panel of the result object of the feedback-loop in this case the Trail-Object.

Warning Limitation: You can only render to image sequences. To abort you need switch from render view to the main window before pressing escape. Could need some improvement … but yeah at least an hack for an hack.



The method I use here generates motion paths which you can render out the whole animation with. i.e. you only need to perform the “apply” once at the end of the animation and you’re good-to-go…

The method you use is perfect for the type of animation required for @higgsas’ method where every frame generates a new state.


1 Like

Yeah i knew i missed something here, it was already a bit late yesterday!

Also inserted some bugs in the end as i wanted to improve the ui feedback and then it did not recognize that it was not working anymore, because your file was already rendering without the script :man_facepalming:. (Tested it before the so called “improvements” on my own feedback hack file)

…so sorry for those who may tried the script already…here is the new fixed version(EDIT: double fixed now, sorry again):

import bpy
import time

"""  Not quick anymore, but still dirty script to render geometry nodes feedback hack in cycles
     Run script to register operator and add button
     To start press the new operator button in the modifier panel
     of the result object of the feedback-loop
     To abort the rendering process you have to switch to the main window and 
     then press ESC """
class RenderFeedbackHack(bpy.types.Operator):
    bl_idname = "object.render_feddback_hack"
    bl_label = "Render Feedback Hack"

    def poll(cls, context):
        return context.active_object is not None and is not None

    def post(self, scene, context):
        self.rendering = False
        self.post_render_first = True

    def execute(self, context):
        scene = context.scene

        # get start and end frames 
        self.end = scene.frame_end + 1
        self.frame = context.scene.frame_start -1
        # get existing output path
        self.fp = context.scene.render.filepath
        scene.render.filepath = self.fp
        self.rendering = False
        self.post_render_first = True
        self.copy_obj = None
        self.copies = []
        #add handlers
        args = (self, scene)     
        self._timer = context.window_manager.event_timer_add(0.5, window=context.window)    
        return {'RUNNING_MODAL'}    
    def modal(self, context, event):
        # abort 
        if event.type in { 'ESC'}:
            if event.value == 'RELEASE':
                return {'CANCELLED'}
        if self.rendering:
            return {'PASS_THROUGH'}
        scene = context.scene
         # get existing output path
        result_obj = context.active_object
        if self.frame < self.end:
            #update frame and scene first ...
            #it seems blender needs this intermediate step to be sure next render starts 
            if self.post_render_first:   
                self.frame += 1
                #remove old object from last rendered frame if it exists
                if self.copy_obj:
                    self.copy_obj.hide_render = True
                #copy and link object
                result_obj.hide_render = False
                self.copy_obj = result_obj.copy()
                scene_col = context.scene.collection
                #apply modifier
                mod =
                with bpy.context.temp_override(active_object = self.copy_obj,object = self.copy_obj):
                scene.render.filepath = self.fp + "{:03d}".format(self.frame)
                result_obj.hide_render = True
                self.post_render_first = False    
                return {'RUNNING_MODAL'}
            #.... in the next iterarion start the new render process
            bpy.ops.render.render("INVOKE_DEFAULT",use_viewport=True,  write_still=True)
            self.rendering = True
            return {'PASS_THROUGH'}
        # cleanup
        result_obj.hide_render = False       
        return {'FINISHED'}
    def cleanup(self, context):
        #remove all the (mostly hidden) copies in the end
        for copy_obj in self.copies:
  , do_unlink=True)    
        context.scene.render.filepath = self.fp
    def remove_handler(self, context):
def menu_func(self, context):
    layout = self.layout
    box =
    row = box.row()

# Register and add a button to the modifier panel
def register():

def unregister():

if __name__ == "__main__":

…also updated the file above with the new version(But as zeroskilz said not needed there).


Should have done more tests: The above interactive version of the script causes those feedback network to get out of sync or even worse let blender crash sometimes from time to time.

It seems to run stable if you change the invocation method by changing the line:

bpy.ops.render.render("INVOKE_DEFAULT",use_viewport=True,  write_still=True)


bpy.ops.render.render(use_viewport=True,  write_still=True) 

Which leads to the fact that the render-operation will run to the end before the script continues. But that approach looses the possibility to view whats going on in the render and also the ability to abort the process. Which is a pity again.

Had no success to get the interactive version working yesterday. And came to the conclusion: When already dealing with python it may be better to just create a buffer object in python.

…and sorry for hijacking this thread it meant to be a short interlude not a never-ending story.

1 Like

A bit sleep could do wonder fixed the script again (hopefully for the last time). Now the interactive version passes these tests:

Main problem was that i was changing data in the app handlers. Should have known. Maybe i did a bit to much geometry-nodes lately and to less scripting.

Updated script above again to the newest version.