Dockable Window that Adds Keyframes - Cannot Access Track Motion

Phil_P wrote on 8/27/2025, 8:26 AM

HI experts,

Background

I wrote series of script (myself) a while ago that give me the option to add 2 track motion keyframes a certain time apart. The first key frame will always be the same value as the previous one and the second will reset to 0. This is really useful to me, as I do a lot of zooms and wanted to keep them standard.

So my workflow, (for example) is:

  • Use the script that inserts 2 keyframes exactly 1 second apart
  • Edit the zoom factor on the second KF that was placed by the previous step
  • When I am ready to zoom back out, run the script again, which inserts 2 KFs, the first one will match the previous zoom factor, and the second one will reset the box to normal

This works great. But occasionally I want to change the values. So I decided I wanted a dockable panel that will sit in Vegas allow me to just click it to do the same job as above. But also, if required I can adjust the timing and settings.

I have never used dockable panels in my scripts before, so I admit I have been using Chat GPT here to help me. The basics that it did are fine. The panel works and comes up in the Extension menu etc.

But the scripting fails. (After hours of trying various things, I am stuck)
It will not allow me to actually insert the keyframes. The basic errors are along the lines of "Cannot open Track Motion"

Well, the project contains 2 files and here they are. The extension compiles with no errors. But it simply does not work when attempting to actually insert the keyframes.

Can anyone see where I am going wrong? Thank you in advance.

KeyframeHelperDock,cs:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ScriptPortal.Vegas;

public class KeyframeHelperDock : DockableControl
{
    private Vegas _vegas => KeyframeHelperModule.VegasInstance;

    private NumericUpDown nudSeconds;
    private ComboBox cboType;
    private Button btnAdd;

    private readonly Dictionary<string, VideoKeyframeType> _typeMap =
        new Dictionary<string, VideoKeyframeType>(StringComparer.OrdinalIgnoreCase)
        {
            ["Linear"] = VideoKeyframeType.Linear,
            ["Hold"] = VideoKeyframeType.Hold,
            ["Slow"] = VideoKeyframeType.Slow,
            ["Fast"] = VideoKeyframeType.Fast,
            ["Smooth"] = VideoKeyframeType.Smooth,
            ["Sharp"] = VideoKeyframeType.Sharp,
        };

    public KeyframeHelperDock() : base("KeyframeHelperDock")
    {
        Text = "Keyframe Helper";
        BuildUI();
    }

    public Guid PersistentID => new Guid("3F6B0C6E-0E7A-4E7C-B0F4-5B0C3B3B9F11");

    private void BuildUI()
    {
        nudSeconds = new NumericUpDown
        {
            Minimum = 0,
            Maximum = 600,
            DecimalPlaces = 2,
            Increment = 0.10M,
            Value = 1.50M,
            Dock = DockStyle.Top
        };

        cboType = new ComboBox
        {
            Dock = DockStyle.Top,
            DropDownStyle = ComboBoxStyle.DropDownList
        };
        cboType.Items.AddRange(new object[] { "Linear", "Hold", "Slow", "Fast", "Smooth", "Sharp" });
        cboType.SelectedIndex = 4; // Smooth

        btnAdd = new Button { Text = "Add Keyframes", Dock = DockStyle.Top };
        btnAdd.Click += (s, e) =>
        {
            var name = (string)cboType.SelectedItem;
            var type = _typeMap.TryGetValue(name, out var t) ? t : VideoKeyframeType.Linear;
            AddKeyframes((double)nudSeconds.Value, type);
        };

        Controls.Add(btnAdd);
        Controls.Add(new Label { Text = "Keyframe Type:", Dock = DockStyle.Top });
        Controls.Add(cboType);
        Controls.Add(new Label { Text = "Seconds:", Dock = DockStyle.Top });
        Controls.Add(nudSeconds);
        Padding = new Padding(8);
    }

    private void AddKeyframes(double seconds, VideoKeyframeType type)
    {
        try
        {
            if (_vegas == null) { MessageBox.Show("VEGAS unavailable."); return; }
            if (_vegas.Project == null) { MessageBox.Show("No project open."); return; }

            // Select video track: prefer selected, else first
            VideoTrack videoTrack = null;
            foreach (Track t in _vegas.Project.Tracks)
                if (t.IsVideo() && t.Selected) { videoTrack = (VideoTrack)t; break; }
            if (videoTrack == null)
                videoTrack = _vegas.Project.Tracks.Count > 0 ? _vegas.Project.Tracks[0] as VideoTrack : null;

            if (videoTrack == null) { MessageBox.Show("No video track found."); return; }

            var cursor = _vegas.Transport.CursorPosition;

            // Acquire Track Motion (this is where COM exception happens in the dock)
            TrackMotion tm;
            try
            {
                tm = videoTrack.TrackMotion;
            }
            catch (COMException comx)
            {
                MessageBox.Show("Track Motion could not be opened on this track.\n\n" + comx.Message,
                    "Track Motion Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (tm == null) { MessageBox.Show("Track Motion unavailable on this track."); return; }

            // --- Read previous KF values ---
            double prevX = 0, prevY = 0, prevW = 0, prevH = 0;
            foreach (TrackMotionKeyframe k in tm.MotionKeyframes)
            {
                if (k.Position <= cursor)
                {
                    prevX = k.PositionX; prevY = k.PositionY; prevW = k.Width; prevH = k.Height;
                }
                else break;
            }

            // First keyframe
            var kf1 = tm.InsertMotionKeyframe(cursor);
            kf1.PositionX = prevX; kf1.PositionY = prevY;
            kf1.Width = prevW; kf1.Height = prevH;
            kf1.Type = type;

            // Second keyframe
            var later = cursor + Timecode.FromSeconds(seconds);
            var kf2 = tm.InsertMotionKeyframe(later);

            int vW = _vegas.Project.Video?.Width ?? 1920;
            int vH = _vegas.Project.Video?.Height ?? 1080;
            kf2.PositionX = 0; kf2.PositionY = 0;
            kf2.Width = vW; kf2.Height = vH;

            _vegas.Transport.CursorPosition = kf2.Position;
        }
        catch (Exception ex)
        {
            MessageBox.Show("Keyframe Helper error:\n\n" + ex, "Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

KeyframeHelperModule.cs
 

using System;
using System.Collections;              // VEGAS 22 uses non-generic ICollection
using ScriptPortal.Vegas;

public class KeyframeHelperModule : ICustomCommandModule
{
    internal static Vegas VegasInstance;

    public void InitializeModule(Vegas vegas)
    {
        VegasInstance = vegas;
    }

    public ICollection GetCustomCommands()
    {
        var cmd = new CustomCommand(CommandCategory.Tools, "KeyframeHelper");
        cmd.DisplayName = "Keyframe Helper";
        cmd.Invoked += (s, e) =>
        {
            VegasInstance.LoadDockView(new KeyframeHelperDock());
        };
        return new CustomCommand[] { cmd };
    }
}

Comments

Phil_P wrote on 8/27/2025, 8:29 AM

Here is the error. The track is just a normal video track. Nothing special. And I can manually open Track Motion no issues and my old script (with no dockable window) works fine.

zzzzzz9125 wrote on 8/27/2025, 8:54 AM

@Phil_P This seems like a very basic problem: you haven't added an UndoBlock to it.

If you are trying to modify anything in the project via Extension, you must wrap this code with an UndoBlock:

using (UndoBlock undo = new UndoBlock(myVegas.Project, "UndoBlock Label"))
{
    ...
}

 

In your code, you need to change to:

        btnAdd.Click += (s, e) =>
        {
            var name = (string)cboType.SelectedItem;
            var type = _typeMap.TryGetValue(name, out var t) ? t : VideoKeyframeType.Linear;
            using (UndoBlock undo = new UndoBlock(_vegas.Project, "UndoBlock Label"))
            {
                AddKeyframes((double)nudSeconds.Value, type);
            }
        };

Last changed by zzzzzz9125 on 8/27/2025, 8:59 AM, changed a total of 2 times.

Using VEGAS Pro 22 build 250 & VEGAS Pro 21 build 208.

Information about my PC:
Brand Name: HP VICTUS Laptop
System: Windows 11.0 (64-bit) 10.00.22631
CPU: 12th Gen Intel(R) Core(TM) i7-12700H
GPU: NVIDIA GeForce RTX 3050 Laptop GPU
GPU Driver: NVIDIA Studio Driver 560.70

Phil_P wrote on 8/27/2025, 9:00 AM

Problem solved! Thank you so much. Highly appreciated.

jetdv wrote on 8/27/2025, 10:51 AM

A custom command/extension cannot modify the timeline in any way unless the "UndoBlock" is used. You can "Read" information from the timeline but you cannot "Change" information on the timeline unless the UndoBlock is used. Plus, that gives the "Undo" list a name to list for undoing that task.