How to Offset All Animated Parameters Using Python?

   1619   10   2
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
I'd like to be able to find all nodes that have animation (keyframes) on their parameters and then offset them by a specified amount.
Unless I'm missing something, there doesn't appear to be a python function that returns a list of all keyframes on all parameters for selected nodes.

Here is what I've discovered thus far on how to access all keyframes for a single specified parameter:

b = hou.node('/obj/box1')
t = hou.parmTuple('/obj/box1/t')
tx = t[0]
tx_keyFrames = tx.keyframes()

tx_keyFrames
(<hou.Keyframe t=0 expr='bezier()' lang=exprLanguage.Hscript v=0 s=0 auto_slopes
=True a=0.333333 use_accel_ratio=True>, <hou.Keyframe t=1 expr='bezier()' lang=e
xprLanguage.Hscript v=2.36562 s=2.00002 auto_slopes=True in a=0.745363 out a=1.4
6588 use_accel_ratio=True>, <hou.Keyframe t=2.96667 expr='bezier()' lang=exprLan
guage.Hscript v=5.47767 s=0 auto_slopes=True in a=0.655556 out a=0.333333 use_ac
cel_ratio=True>)

It seems like it would require a bit of coding to traverse all nodes to collect all keyframes for all parameters.
Does anyone know of HOU python functions that does this, so I'm not reinventing the wheel?

Thanks
User Avatar
Member
311 posts
Joined: 10月 2016
Offline
syntheticperson
I'd like to be able to find all nodes that have animation (keyframes) on their parameters and then offset them by a specified amount.
Unless I'm missing something, there doesn't appear to be a python function that returns a list of all keyframes on all parameters for selected nodes.

Here is what I've discovered thus far on how to access all keyframes for a single specified parameter:

b = hou.node('/obj/box1')
t = hou.parmTuple('/obj/box1/t')
tx = t[0]
tx_keyFrames = tx.keyframes()

tx_keyFrames
(<hou.Keyframe t=0 expr='bezier()' lang=exprLanguage.Hscript v=0 s=0 auto_slopes
=True a=0.333333 use_accel_ratio=True>, <hou.Keyframe t=1 expr='bezier()' lang=e
xprLanguage.Hscript v=2.36562 s=2.00002 auto_slopes=True in a=0.745363 out a=1.4
6588 use_accel_ratio=True>, <hou.Keyframe t=2.96667 expr='bezier()' lang=exprLan
guage.Hscript v=5.47767 s=0 auto_slopes=True in a=0.655556 out a=0.333333 use_ac
cel_ratio=True>)

It seems like it would require a bit of coding to traverse all nodes to collect all keyframes for all parameters.
Does anyone know of HOU python functions that does this, so I'm not reinventing the wheel?

Thanks



Hi synteticperson and everyone interested. No, I'm not aware of any tool for that, but maybe there is one. You could try placing the following code in your shelf. It is about half a day's work of researching the API and coding. Any bug reports or RFE:s may be submitted to me. I hope you like it and find use for it!

To use: Paste the code in a shelf and name it whatever you like. For example "offset keyframes"



def offsetKeyframes():
    '''Code for offsetting all keyframed parms of a selected node. Version 1.0 beta. For the Houdini community By SWest 2023'''
    inputs_validated = True

    
    selected_nodes = hou.selectedNodes()
    if selected_nodes == ():
        inputs_validated = False        
    if not inputs_validated:
        message='This tool will offset all scoped keyframes for a node. Please select a node with keyframes and try again. '
        hou.ui.displayMessage(message)    


    if(inputs_validated):
        message = 'Please type a positive or negative number for frame offset. Example -10 or 15.5'
        offset = hou.ui.readInput(message, buttons=('OK',), severity=hou.severityType.Message, default_choice=0, close_choice=-1, help=None, title=None, initial_contents=None) 
        try:
            offset = float(offset[1])
        except:
            print('There was an error with the input number.')
            inputs_validated = False

            
    if(inputs_validated):
        for node in selected_nodes:
            for parm in node.allParms():
                parm_is_scoped = parm.isScoped()
                parm_keyframes_length = 0
                try:
                    parm_keyframes = parm.keyframes()
                except:
                    continue
                else:
                    parm_keyframes_length=len(parm_keyframes)
                if(parm_keyframes_length > 0 and parm_is_scoped):
                    print(parm.path())
                    for i in range(0,parm_keyframes_length):
                        keyframe_dict=parm.keyframes()[i].asJSON(save_keys_in_frames=True)
                        frame_in=keyframe_dict['frame']
                        frame_out=frame_in+offset
                        keyframe_dict['frame'] = frame_out
                        keyframe_out = hou.Keyframe()
                        keyframe_out.fromJSON(keyframe_dict)
                        parm.setKeyframe(keyframe_out)
                        parm.deleteKeyframeAtFrame(frame_in)
    
                       
offsetKeyframes()
Edited by SWest - 2023年3月26日 16:15:43
Interested in character concepts, modeling, rigging, and animation. Related tool dev with Py and VEX.
User Avatar
Member
251 posts
Joined: 7月 2013
Offline
I once made this shelf snippet 2000 years ago but it still works. Uses a bit of python to gather the animated parms, then some hscript to move the keyframes. It only shifts keyframes after the current playbar time.

import hou

res = hou.ui.readInput("Frames to shift after current frame", buttons=("OK", "Cancel"))

if res[0] == 0:
nodes=hou.node("/obj").allSubChildren()
cframe=hou.frame()

shift=int(res[1])

animNodes=[]
for item in nodes:
for p in item.parms():
if len(p.keyframes())>1:

if not item in animNodes:
animNodes.append(item)

with hou.undos.group("Shift Keyframes"):
for item in animNodes:
print item.path()
hou.hscript( "chkeymv -r "+item.path()+"/* "+str(cframe)+" 20000 "+str(cframe+shift)+" "+str(20000+shift) )

edit: those nested python for-loops could be a oneliner.. old code
Edited by Jonathan de Blok - 2023年3月26日 17:12:30
More code, less clicks.
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
Hi SWest and Jonathan, great! I'll try your suggestions and will let you know what works. Many thanks!
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
Hi SWest, I tried to create a shelf tool with your code, and test it on an animated cube, but I get these errors:

Traceback (most recent call last):
File "OffsetKeyFrames", line 47, in <module>
File "OffsetKeyFrames", line 38, in offsetKeyframes
IndexError: tuple index out of range

Which I traced back to this line in your code:

Line 38: keyframe_dict=parm.keyframes()[i].asJSON(save_keys_in_frames=True)

Any ideas?

Thanks
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
Hi Jonathan de Blok, I managed to get your version working. Thanks.
However, it would be nice if it supported the /stage context as well as /obj
User Avatar
Member
251 posts
Joined: 7月 2013
Offline
syntheticperson
Hi Jonathan de Blok, I managed to get your version working. Thanks.
However, it would be nice if it supported the /stage context as well as /obj

Cleaned it up a little and for fun sort of made it into a one liner If you place it into a shelf tool just clicking it will run on "(/obj)", shift-clicking will run on all contexts ("/") including "/stage"

import hou

#for debugging, can be omitted when used as shelf tool as that will populate the kwargs dict for us
if not "kwargs" in globals().keys():
    kwargs={}
    kwargs["shiftclick"]=False
    

res = hou.ui.readInput("Frames to shift keyframes after current frame", buttons=("OK", "Cancel"))

if res[0] == 0 and res[1].replace("-","").isnumeric():
    currentFrame=hou.frame()
    shift= int(res[1])
    with hou.undos.group("Shift Keyframes"):                
        list(map(lambda animObj:  hou.hscript( "chkeymv -r "+animObj.path()+"/* "+str(currentFrame)+" 20000 "+str(currentFrame+shift)+" "+str(20000+shift) ),  list(filter(lambda  node:  len(list(filter(lambda parm: len(parm.keyframes())>1, node.parms() ))), hou.node("/" if kwargs["shiftclick"] else "/obj").allSubChildren() )))) 

# this gets all nodes from /obj or /  (every context) depending on shiftclick or not:   
# hou.node("/" if kwargs["shiftclick"] else "/obj").allSubChildren()

# This filters the nodes from the above list to those that have parms that have more then 1 keyframe (parms with expression have a keyframe as well, and 1 keyframe parms are not animated since it will yield a contant value thus only focus on those with more then 1 keyframe):
# list(filter(lambda  node:  len(list(filter(lambda parm: len(parm.keyframes())>1, node.parms() )))

# The 'map' function then runs each node from that filtered list through a small lambda function that sets up and executes the hscript command 'chkeymv' with the correct settings:
# map(lambda animObj:  hou.hscript( "chkeymv -r "+animObj.path()+"/* "+str(currentFrame)+" 20000 "+str(currentFrame+shift)+" "+str(20000+shift) )

# The map function creates an iterator object that doesn't loop over all the items by itself so we force it to yield by converting it into a list. The generated list itself isn't used
Edited by Jonathan de Blok - 2023年3月29日 08:51:01
More code, less clicks.
User Avatar
Member
311 posts
Joined: 10月 2016
Offline


Which I traced back to this line in your code:

Line 38: keyframe_dict=parm.keyframes()[i].asJSON(save_keys_in_frames=True)

Any ideas?

Thanks

Yes, this error does not appear on my system which make bug tracking a bit challenging. However I have some ideas, but at the moment the timing for this project is not perfect since I'm really busy with other things, for example paid projects.

However, if I ever go back to actually animation probably I'll look into some tools for keyframes.

So good Jonathan was able to respond quicker. Hat off and cheers!
Interested in character concepts, modeling, rigging, and animation. Related tool dev with Py and VEX.
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
Jonathan de Blok
syntheticperson
Hi Jonathan de Blok, I managed to get your version working. Thanks.
However, it would be nice if it supported the /stage context as well as /obj

Cleaned it up a little and for fun sort of made it into a one liner If you place it into a shelf tool just clicking it will run on "(/obj)", shift-clicking will run on all contexts ("/") including "/stage"

import hou

#for debugging, can be omitted when used as shelf tool as that will populate the kwargs dict for us
if not "kwargs" in globals().keys():
    kwargs={}
    kwargs["shiftclick"]=False
    

res = hou.ui.readInput("Frames to shift keyframes after current frame", buttons=("OK", "Cancel"))

if res[0] == 0 and res[1].replace("-","").isnumeric():
    currentFrame=hou.frame()
    shift= int(res[1])
    with hou.undos.group("Shift Keyframes"):                
        list(map(lambda animObj:  hou.hscript( "chkeymv -r "+animObj.path()+"/* "+str(currentFrame)+" 20000 "+str(currentFrame+shift)+" "+str(20000+shift) ),  list(filter(lambda  node:  len(list(filter(lambda parm: len(parm.keyframes())>1, node.parms() ))), hou.node("/" if kwargs["shiftclick"] else "/obj").allSubChildren() )))) 

# this gets all nodes from /obj or /  (every context) depending on shiftclick or not:   
# hou.node("/" if kwargs["shiftclick"] else "/obj").allSubChildren()

# This filters the nodes from the above list to those that have parms that have more then 1 keyframe (parms with expression have a keyframe as well, and 1 keyframe parms are not animated since it will yield a contant value thus only focus on those with more then 1 keyframe):
# list(filter(lambda  node:  len(list(filter(lambda parm: len(parm.keyframes())>1, node.parms() )))

# The 'map' function then runs each node from that filtered list through a small lambda function that sets up and executes the hscript command 'chkeymv' with the correct settings:
# map(lambda animObj:  hou.hscript( "chkeymv -r "+animObj.path()+"/* "+str(currentFrame)+" 20000 "+str(currentFrame+shift)+" "+str(20000+shift) )

# The map function creates an iterator object that doesn't loop over all the items by itself so we force it to yield by converting it into a list. The generated list itself isn't used

Thanks, Jonathan. I'll give that a try.
Edited by syntheticperson - 2023年3月30日 14:11:36
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
SWest


Which I traced back to this line in your code:

Line 38: keyframe_dict=parm.keyframes()[i].asJSON(save_keys_in_frames=True)

Any ideas?

Thanks

Yes, this error does not appear on my system which make bug tracking a bit challenging. However I have some ideas, but at the moment the timing for this project is not perfect since I'm really busy with other things, for example paid projects.

However, if I ever go back to actually animation probably I'll look into some tools for keyframes.

So good Jonathan was able to respond quicker. Hat off and cheers!

No worries, SWest. I totally understand.
Thanks
User Avatar
Member
159 posts
Joined: 7月 2010
Offline
Hi Jonathan De Blok, your offset keyframes script works great!
However, I neglected to clarify that I would like it to find all animated keyframes of all children inside /obj and /stage.
Please find attached a houdini file as an example.

/obj/box/matnet1/box_principledshader1 has Base Color animated keyframes.
It would be great if these could be offset as well.

The same goes for:

/stage/materiallibrary1/torus_mtlxstandard_surface

Thanks
Edited by syntheticperson - 2023年3月30日 14:33:38

Attachments:
OffsetKeyFrames.v004.hipnc (452.3 KB)

  • Quick Links