Ben Traje
← Back to mograph

Keyframe Cloner Offset Based on the Number of Clones in Cinema4D

29 Apr 26 (5mo ago)

With a the cloner set to Objec Type, filled with a spline, I want to have an even rudimentary cycle of the clones as it cycles through the spline.

To automate that, here is the script that does that:

import c4d

def main():
    # 1. Get the active selected Object
    obj = doc.GetActiveObject()
    
    # Check if an object is selected and if it's a Cloner (Type ID 1018544)
    if obj is None or obj.GetType() != 1018544:
        c4d.gui.MessageDialog("Please select a Cloner object in your Object Manager.")
        return
    
    # 2. Extract the Cloner count data
    try:
        clone_count = obj[c4d.MG_SPLINE_COUNT]
    except AttributeError:
        c4d.gui.MessageDialog("Could not read MG_SPLINE_COUNT. Make sure the Cloner is set up correctly.")
        return

    if clone_count is None or clone_count < 2:
        c4d.gui.MessageDialog("You need a Count of at least 2 to animate the offset intervals.")
        return

    # 3. Time and Frame Calculations
    fps = doc.GetFps()
    min_time = doc.GetMinTime().GetFrame(fps)
    intervals = clone_count - 1
    
    # --- NEW LOGIC: Hardcoded Hold and Transition Frames ---
    hold_frames = 20
    transition_frames = 8
    
    # Start an undo block
    doc.StartUndo()
    
    # 4. Track Creation for Offset
    offset_desc_id = c4d.DescID(c4d.DescLevel(c4d.MG_SPLINE_OFFSET, c4d.DTYPE_REAL, 0))
    track = obj.FindCTrack(offset_desc_id)
    
    if track:
        doc.AddUndo(c4d.UNDOTYPE_DELETE, track)
        track.Remove() 
        
    track = c4d.CTrack(obj, offset_desc_id)
    obj.InsertTrackSorted(track) 
    doc.AddUndo(c4d.UNDOTYPE_NEW, track)
    
    curve = track.GetCurve()

    # 5. Loop and Insert Double Keyframes (Start and End of Hold)
    for idx in range(clone_count):
        # Maps step index to a 0.0 -> 1.0 float (0% -> 100% in the UI)
        offset_value = float(idx) / float(intervals) 
        
        # Calculate exactly when this position's hold period starts and ends
        hold_start_frame = min_time + (idx * (hold_frames + transition_frames))
        hold_end_frame = hold_start_frame + hold_frames
        
        # --- KEY 1: Start of the hold ---
        start_time = c4d.BaseTime(hold_start_frame / float(fps))
        key_dict_start = curve.AddKey(start_time)
        if key_dict_start:
            k_start = key_dict_start["key"]
            k_start.SetValue(curve, offset_value)
            k_start.SetInterpolation(curve, c4d.CINTERPOLATION_LINEAR)
            
        # --- KEY 2: End of the hold ---
        end_time = c4d.BaseTime(hold_end_frame / float(fps))
        key_dict_end = curve.AddKey(end_time)
        if key_dict_end:
            k_end = key_dict_end["key"]
            k_end.SetValue(curve, offset_value)
            k_end.SetInterpolation(curve, c4d.CINTERPOLATION_LINEAR)

    # 6. Auto-expand timeline if the keys go past the current max time
    current_max_frame = doc.GetMaxTime().GetFrame(fps)
    if hold_end_frame > current_max_frame:
        new_max_time = c4d.BaseTime(hold_end_frame / float(fps))
        doc.SetMaxTime(new_max_time)

    # Close Undo block, refresh the view manager UI
    doc.EndUndo()
    c4d.EventAdd()
    print(f"Successfully generated step-animation with {hold_frames}-frame holds and {transition_frames}-frame transitions!")

if __name__ == '__main__':
    main()