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.
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:
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.
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 . (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):
"""Tooltip"""
bl_idname = "object.render_feddback_hack"
bl_label = "Render Feedback Hack"
@classmethod
def poll(cls, context):
return context.active_object is not None and context.scene.camera 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)
bpy.app.handlers.render_post.append(self.post)
self._timer = context.window_manager.event_timer_add(0.5, window=context.window)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modal(self, context, event):
# abort
if event.type in { 'ESC'}:
if event.value == 'RELEASE':
self.cleanup(context)
self.report({'WARNING'},"ABORTED")
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
scene.frame_set(self.frame)
#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()
if result_obj.data:
self.copy_obj.data = result_obj.data.copy()
scene_col = context.scene.collection
scene_col.objects.link(self.copy_obj)
self.copies.append(self.copy_obj)
#apply modifier
mod = self.copy_obj.modifiers.active
with bpy.context.temp_override(active_object = self.copy_obj,object = self.copy_obj):
bpy.ops.object.modifier_apply(modifier=mod.name)
scene.render.filepath = self.fp + "{:03d}".format(self.frame)
result_obj.hide_render = True
context.view_layer.update()
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
self.cleanup(context)
return {'FINISHED'}
def cleanup(self, context):
#remove all the (mostly hidden) copies in the end
for copy_obj in self.copies:
bpy.data.objects.remove(copy_obj, do_unlink=True)
context.scene.render.filepath = self.fp
self.remove_handler(context)
def remove_handler(self, context):
bpy.app.handlers.render_post.remove(self.post)
context.window_manager.event_timer_remove(self._timer)
def menu_func(self, context):
layout = self.layout
box = layout.box()
row = box.row()
row.operator("object.render_feddback_hack")
# Register and add a button to the modifier panel
def register():
bpy.utils.register_class(RenderFeedbackHack)
bpy.types.DATA_PT_modifiers.append(menu_func)
def unregister():
bpy.utils.unregister_class(RenderFeedbackHack)
bpy.types.DATA_PT_modifiers.remove(menu_func)
if __name__ == "__main__":
register()
…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:
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.
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.