Ejemplo n.º 1
0
//----------------------------------------------------------------------------------------------------------------------

        internal static void LockAndEditPlayableFrame(SISPlayableFrame playableFrame,
                                                      RenderCachePlayableAsset renderCachePlayableAsset)
        {
            int    index    = playableFrame.GetIndex();
            string filePath = renderCachePlayableAsset.GetImageFilePath(index);

            if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
            {
                EditorUtility.DisplayDialog(StreamingImageSequenceConstants.DIALOG_HEADER,
                                            "Please update RenderCachePlayableAsset.",
                                            "Ok");
                return;
            }

            string fullPath = Path.GetFullPath(filePath);

            playableFrame.SetLocked(true);
            string imageAppPath = EditorPrefs.GetString("kImagesDefaultApp");

            if (string.IsNullOrEmpty(imageAppPath) || !File.Exists(imageAppPath))
            {
                System.Diagnostics.Process.Start(fullPath);
                return;
            }

            System.Diagnostics.Process.Start(imageAppPath, fullPath);
        }
Ejemplo n.º 2
0
    static void LockAndEditFrame(ShortcutArguments args) {
        
        foreach (Object obj in Selection.objects) {
            FrameMarker frameMarker = obj as FrameMarker;
            if (null == frameMarker) {
                continue;
            }
            SISPlayableFrame playableFrame       = frameMarker.GetOwner();
            RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset<RenderCachePlayableAsset>();            
            if (null == playableAsset)
                return;
        
            FrameMarkerInspector.LockAndEditPlayableFrame(playableFrame, playableAsset);

        }        
                        
    }    
//----------------------------------------------------------------------------------------------------------------------

        internal static void LockAndEditPlayableFrame(SISPlayableFrame playableFrame,
                                                      RenderCachePlayableAsset renderCachePlayableAsset)
        {
            int    index    = playableFrame.GetIndex();
            string filePath = renderCachePlayableAsset.GetImageFilePath(index);

            if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
            {
                EditorUtility.DisplayDialog(StreamingImageSequenceConstants.DIALOG_HEADER,
                                            "Please update RenderCachePlayableAsset.",
                                            "Ok");
                return;
            }

            playableFrame.SetLocked(true);
            LaunchImageApplicationExternalTool(Path.GetFullPath(filePath));
        }
//----------------------------------------------------------------------------------------------------------------------    
    public override void DrawOverlay(IMarker m, MarkerUIStates uiState, MarkerOverlayRegion region)
    {
        FrameMarker marker = m as FrameMarker;
        if (null == marker)
            return;

        SISPlayableFrame playableFrame = marker.GetOwner();
        
        //Check invalid PlayableFrame/ClipData. Perhaps because of unsupported Duplicate operation ?
        PlayableFrameClipData clipData = playableFrame?.GetOwner();
        if (clipData == null)
            return;
        
        PlayableFramePropertyID inspectedPropertyID = clipData.GetInspectedProperty();
        switch (inspectedPropertyID) {
            case PlayableFramePropertyID.USED: {
                
                if (playableFrame.IsLocked()) {
                    //At the moment, all locked frames are regarded as inactive 
                    if (playableFrame.IsUsed()) {
                        Graphics.DrawTexture(region.markerRegion, EditorTextures.GetInactiveCheckedTexture());
                    }
                    Rect lockRegion = region.markerRegion;
                    lockRegion.x -= 5;
                    lockRegion.y -= 8;
                    Graphics.DrawTexture(lockRegion, EditorTextures.GetLockTexture());                    
                } else {
                    if (playableFrame.IsUsed()) {
                        Graphics.DrawTexture(region.markerRegion, EditorTextures.GetCheckedTexture());
                    }
                    
                }
                break;
            }
            case PlayableFramePropertyID.LOCKED: {
                if (playableFrame.IsLocked()) {
                    Graphics.DrawTexture(region.markerRegion, EditorTextures.GetLockTexture());                    
                }
                break;
            }
            
        }
        
    }
Ejemplo n.º 5
0
        internal static void ToggleMarkerValueByContext(FrameMarker frameMarker)
        {
            SISPlayableFrame        playableFrame       = frameMarker.GetOwner();
            TimelineClipSISData     timelineClipSISData = playableFrame.GetOwner();
            PlayableFramePropertyID inspectedPropertyID = timelineClipSISData.GetInspectedProperty();

            switch (inspectedPropertyID)
            {
            case PlayableFramePropertyID.USED: {
                playableFrame.SetUsed(!playableFrame.IsUsed());
                break;
            }

            case PlayableFramePropertyID.LOCKED: {
                playableFrame.SetLocked(!playableFrame.IsLocked());
                break;
            }
            }
        }
Ejemplo n.º 6
0
//----------------------------------------------------------------------------------------------------------------------
        private static void SetMarkerValueByContext(FrameMarker frameMarker, bool value)
        {
            SISPlayableFrame        playableFrame       = frameMarker.GetOwner();
            TimelineClipSISData     timelineClipSISData = playableFrame.GetOwner();
            PlayableFramePropertyID inspectedPropertyID = timelineClipSISData.GetInspectedProperty();

            switch (inspectedPropertyID)
            {
            case PlayableFramePropertyID.USED: {
                playableFrame.SetUsed(value);
                break;
            }

            case PlayableFramePropertyID.LOCKED: {
                playableFrame.SetLocked(value);
                break;
            }
            }
        }
//----------------------------------------------------------------------------------------------------------------------
        public override void OnInspectorGUI()
        {
            ShortcutBinding useFrameShortcut
                = ShortcutManager.instance.GetShortcutBinding(SISEditorConstants.SHORTCUT_TOGGLE_FRAME_MARKER);
            bool prevUseFrame = m_assets[0].IsFrameUsed();
            bool useFrame     = EditorGUILayout.Toggle($"Use Frame ({useFrameShortcut})", prevUseFrame);

            if (useFrame != prevUseFrame)
            {
                //Set all selected objects
                foreach (FrameMarker m in m_assets)
                {
                    SetMarkerValueByContext(m, useFrame);
                }
            }

            //Only show lock and edit for RenderCachePlayableAsset
            foreach (FrameMarker frameMarker in m_assets)
            {
                SISPlayableFrame         playableFrame = frameMarker.GetOwner();
                RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset <RenderCachePlayableAsset>();
                if (null == playableAsset)
                {
                    return;
                }
            }

            //m_assets only contain RenderCachePlayableAsset at this point
            ShortcutBinding lockAndEditShortcut
                = ShortcutManager.instance.GetShortcutBinding(SISEditorConstants.SHORTCUT_LOCK_AND_EDIT_FRAME);

            if (GUILayout.Button($"Lock and Edit ({lockAndEditShortcut})"))
            {
                foreach (FrameMarker frameMarker in m_assets)
                {
                    SISPlayableFrame         playableFrame = frameMarker.GetOwner();
                    RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset <RenderCachePlayableAsset>();
                    Assert.IsNotNull(playableAsset);
                    LockAndEditPlayableFrame(playableFrame, playableAsset);
                }
            }
        }
//----------------------------------------------------------------------------------------------------------------------
        ///<inheritdoc />
        public override ActionValidity Validate(IEnumerable <IMarker> markers)
        {
            foreach (IMarker marker in markers)
            {
                FrameMarker frameMarker = marker as FrameMarker;
                if (null == frameMarker)
                {
                    return(ActionValidity.NotApplicable);
                }


                SISPlayableFrame         playableFrame = frameMarker.GetOwner();
                RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset <RenderCachePlayableAsset>();
                if (null == playableAsset)
                {
                    return(ActionValidity.NotApplicable);
                }
            }
            return(ActionValidity.Valid);
        }
//----------------------------------------------------------------------------------------------------------------------    
    public override void DrawOverlay(IMarker m, MarkerUIStates uiState, MarkerOverlayRegion region)
    {
        FrameMarker marker = m as FrameMarker;
        if (null == marker)
            return;


        SISPlayableFrame playableFrame = marker.GetOwner();
        TimelineClipSISData timelineClipSISData = playableFrame.GetOwner();
        PlayableFramePropertyID inspectedPropertyID = timelineClipSISData.GetInspectedProperty();
        switch (inspectedPropertyID) {
            case PlayableFramePropertyID.USED: {
                
                if (playableFrame.IsLocked()) {
                    //At the moment, all locked frames are regarded as inactive 
                    if (playableFrame.IsUsed()) {
                        Graphics.DrawTexture(region.markerRegion, EditorTextures.GetInactiveCheckedTexture());
                    }
                    Rect lockRegion = region.markerRegion;
                    lockRegion.x -= 5;
                    lockRegion.y -= 8;
                    Graphics.DrawTexture(lockRegion, EditorTextures.GetLockTexture());                    
                } else {
                    if (playableFrame.IsUsed()) {
                        Graphics.DrawTexture(region.markerRegion, EditorTextures.GetCheckedTexture());
                    }
                    
                }
                break;
            }
            case PlayableFramePropertyID.LOCKED: {
                if (playableFrame.IsLocked()) {
                    Graphics.DrawTexture(region.markerRegion, EditorTextures.GetLockTexture());                    
                }
                break;
            }
            
        }
        
    }
        ///<inheritdoc />
        public override bool Execute(IEnumerable <IMarker> markers)
        {
            foreach (IMarker marker in markers)
            {
                FrameMarker frameMarker = marker as FrameMarker;
                if (null == frameMarker)
                {
                    return(false);
                }

                SISPlayableFrame         playableFrame = frameMarker.GetOwner();
                RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset <RenderCachePlayableAsset>();
                if (null == playableAsset)
                {
                    return(false);
                }

                FrameMarkerInspector.LockAndEditPlayableFrame(playableFrame, playableAsset);
            }

            return(true);
        }
        internal static void EditPlayableFrame(SISPlayableFrame playableFrame,
                                               StreamingImageSequencePlayableAsset sisPlayableAsset)
        {
            //Find the correct imageIndex. The number of frames in the clip may be more/less than the number of images
            int playableFrameIndex = playableFrame.GetIndex();
            int numPlayableFrames  = sisPlayableAsset.GetBoundClipData().GetNumPlayableFrames();

            int numImages = sisPlayableAsset.GetNumImages();
            int index     = Mathf.FloorToInt(playableFrameIndex * ((float)numImages / numPlayableFrames));

            string filePath = sisPlayableAsset.GetImageFilePath(index);

            if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
            {
                EditorUtility.DisplayDialog(StreamingImageSequenceConstants.DIALOG_HEADER,
                                            "Image does not exist: " + filePath,
                                            "Ok");
                return;
            }

            LaunchImageApplicationExternalTool(Path.GetFullPath(filePath));
        }
//----------------------------------------------------------------------------------------------------------------------
        internal static IEnumerator UpdateRenderCacheCoroutine(PlayableDirector director, RenderCachePlayableAsset renderCachePlayableAsset)
        {
            Assert.IsNotNull(director);
            Assert.IsNotNull(renderCachePlayableAsset);

            TimelineClipSISData timelineClipSISData = renderCachePlayableAsset.GetBoundTimelineClipSISData();

            if (null == timelineClipSISData)
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            "RenderCachePlayableAsset is not ready",
                                            "Ok");
                yield break;
            }

            TrackAsset         track          = renderCachePlayableAsset.GetBoundTimelineClipSISData().GetOwner().parentTrack;
            BaseRenderCapturer renderCapturer = director.GetGenericBinding(track) as BaseRenderCapturer;

            if (null == renderCapturer)
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            "Please bind an appropriate RenderCapturer component to the track.",
                                            "Ok");
                yield break;
            }


            //begin capture
            bool canCapture = renderCapturer.BeginCapture();

            if (!canCapture)
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            renderCapturer.GetLastErrorMessage(),
                                            "Ok");
                yield break;
            }


            //Check output folder
            string outputFolder = renderCachePlayableAsset.GetFolder();

            if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder))
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            "Invalid output folder",
                                            "Ok");
                yield break;
            }

            Texture capturerTex = renderCapturer.GetInternalTexture();

            //Show progress in game view
            GameObject           progressGo = new GameObject("Blitter");
            LegacyTextureBlitter blitter    = progressGo.AddComponent <LegacyTextureBlitter>();

            blitter.SetTexture(capturerTex);
            blitter.SetCameraDepth(int.MaxValue);

            TimelineClip timelineClip     = timelineClipSISData.GetOwner();
            double       nextDirectorTime = timelineClip.start;
            double       timePerFrame     = 1.0f / track.timelineAsset.editorSettings.fps;

            int fileCounter = 0;
            int numFiles    = (int)Math.Ceiling(timelineClip.duration / timePerFrame) + 1;
            int numDigits   = MathUtility.GetNumDigits(numFiles);

            string        prefix         = $"{timelineClip.displayName}_";
            List <string> imageFileNames = new List <string>(numFiles);

            //Store old files that has the same pattern
            string[]         existingFiles = Directory.GetFiles(outputFolder, $"*.png");
            HashSet <string> filesToDelete = new HashSet <string>(existingFiles);

            bool cancelled = false;

            while (nextDirectorTime <= timelineClip.end && !cancelled)
            {
                string fileName       = $"{prefix}{fileCounter.ToString($"D{numDigits}")}.png";
                string outputFilePath = Path.Combine(outputFolder, fileName);

                SISPlayableFrame playableFrame = timelineClipSISData.GetPlayableFrame(fileCounter);
                bool             captureFrame  = (!timelineClipSISData.AreFrameMarkersRequested() || //if markers are not requested, capture
                                                  !File.Exists(outputFilePath) || //if file doesn't exist, capture
                                                  (null != playableFrame && playableFrame.IsUsed() && !playableFrame.IsLocked())
                                                  );

                if (filesToDelete.Contains(outputFilePath))
                {
                    filesToDelete.Remove(outputFilePath);
                }
                imageFileNames.Add(fileName);

                if (captureFrame)
                {
                    SetDirectorTime(director, nextDirectorTime);

                    //Need at least two frames in order to wait for the TimelineWindow to be updated ?
                    yield return(null);

                    yield return(null);

                    yield return(null);

                    //Unload texture because it may be overwritten
                    StreamingImageSequencePlugin.UnloadImageAndNotify(outputFilePath);
                    renderCapturer.CaptureToFile(outputFilePath);
                }


                nextDirectorTime += timePerFrame;
                ++fileCounter;

                cancelled = EditorUtility.DisplayCancelableProgressBar(
                    "StreamingImageSequence", "Caching render results", ((float)fileCounter / numFiles));
            }

            if (!cancelled)
            {
                renderCachePlayableAsset.SetImageFileNames(imageFileNames);

                //Delete old files
                if (AssetDatabase.IsValidFolder(outputFolder))
                {
                    foreach (string oldFile in filesToDelete)
                    {
                        AssetDatabase.DeleteAsset(oldFile);
                    }
                }
                else
                {
                    foreach (string oldFile in filesToDelete)
                    {
                        File.Delete(oldFile);
                    }
                }
            }

            //Notify
            FolderContentsChangedNotifier.GetInstance().Notify(outputFolder);

            //Cleanup
            EditorUtility.ClearProgressBar();
            renderCapturer.EndCapture();
            ObjectUtility.Destroy(progressGo);

            AssetDatabase.Refresh();

            yield return(null);
        }
Ejemplo n.º 13
0
//----------------------------------------------------------------------------------------------------------------------
        internal static IEnumerator UpdateRenderCacheCoroutine(PlayableDirector director, RenderCachePlayableAsset renderCachePlayableAsset)
        {
            Assert.IsNotNull(director);
            Assert.IsNotNull(renderCachePlayableAsset);

            PlayableFrameClipData clipData = renderCachePlayableAsset.GetBoundClipData();

            if (null == clipData)
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            "RenderCachePlayableAsset is not ready",
                                            "Ok");
                yield break;
            }

            TrackAsset         track          = renderCachePlayableAsset.GetBoundClipData().GetOwner().GetParentTrack();
            BaseRenderCapturer renderCapturer = director.GetGenericBinding(track) as BaseRenderCapturer;

            if (null == renderCapturer)
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            "Please bind an appropriate RenderCapturer component to the track.",
                                            "Ok");
                yield break;
            }

            //Check output folder
            string outputFolder = renderCachePlayableAsset.GetFolder();

            if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder))
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            "Invalid output folder",
                                            "Ok");
                yield break;
            }

            //Check if we can capture
            bool canCapture = renderCapturer.CanCaptureV();

            if (!canCapture)
            {
                EditorUtility.DisplayDialog("Streaming Image Sequence",
                                            renderCapturer.GetLastErrorMessage(),
                                            "Ok");
                yield break;
            }

            //begin capture
            IEnumerator beginCapture = renderCapturer.BeginCaptureV();

            while (beginCapture.MoveNext())
            {
                yield return(beginCapture.Current);
            }

            //Show progress in game view
            Texture capturerTex = renderCapturer.GetInternalTexture();
            RenderCachePlayableAssetEditorConfig editorConfig = renderCachePlayableAsset.GetEditorConfig();
            BaseTextureBlitter blitter         = CreateBlitter(capturerTex);
            Material           blitToScreenMat = renderCapturer.GetOrCreateBlitToScreenEditorMaterialV();

            if (!blitToScreenMat.IsNullRef())
            {
                blitToScreenMat.SetColor(m_bgColorProperty, editorConfig.GetUpdateBGColor());
                blitter.SetBlitMaterial(blitToScreenMat);
            }

            GameObject blitterGO = blitter.gameObject;



            TimelineClip timelineClip = clipData.GetOwner();
            double       timePerFrame = 1.0f / track.timelineAsset.editorSettings.GetFPS();

            //initial calculation of loop vars
            bool captureAllFrames = editorConfig.GetCaptureAllFrames();
            int  fileCounter      = 0;
            int  numFiles         = (int)Math.Ceiling(timelineClip.duration / timePerFrame) + 1;
            int  numDigits        = MathUtility.GetNumDigits(numFiles);

            if (!captureAllFrames)
            {
                fileCounter = editorConfig.GetCaptureStartFrame();
                numFiles    = (editorConfig.GetCaptureEndFrame() - fileCounter) + 1;
                if (numFiles <= 0)
                {
                    EditorUtility.DisplayDialog("Streaming Image Sequence", "Invalid Start/End Frame Settings", "Ok");
                    yield break;
                }
            }
            int captureStartFrame = fileCounter;

            string prefix = $"{timelineClip.displayName}_";
            List <WatchedFileInfo> imageFiles = new List <WatchedFileInfo>(numFiles);

            //Store old files that has the same pattern
            string[]         existingFiles = Directory.GetFiles(outputFolder, $"*.png");
            HashSet <string> filesToDelete = new HashSet <string>(existingFiles);

            RenderCacheOutputFormat outputFormat = renderCachePlayableAsset.GetOutputFormat();
            string outputExt = null;

            switch (outputFormat)
            {
            case RenderCacheOutputFormat.EXR: outputExt = "exr"; break;

            default:                          outputExt = "png"; break;;
            }

            bool cancelled = false;

            while (!cancelled)
            {
                //Always recalculate from start to avoid floating point errors
                double directorTime = timelineClip.start + (fileCounter * timePerFrame);
                if (directorTime > timelineClip.end)
                {
                    break;
                }

                if (!captureAllFrames && fileCounter > editorConfig.GetCaptureEndFrame())
                {
                    break;
                }

                string fileName       = $"{prefix}{fileCounter.ToString($"D{numDigits}")}.{outputExt}";
                string outputFilePath = Path.Combine(outputFolder, fileName);

                SISPlayableFrame playableFrame = clipData.GetPlayableFrame(fileCounter);
                bool             captureFrame  = (!clipData.AreFrameMarkersRequested() || //if markers are not requested, capture
                                                  !File.Exists(outputFilePath) || //if file doesn't exist, capture
                                                  (null != playableFrame && playableFrame.IsUsed() && !playableFrame.IsLocked())
                                                  );

                if (filesToDelete.Contains(outputFilePath))
                {
                    filesToDelete.Remove(outputFilePath);
                }


                if (captureFrame)
                {
                    SetDirectorTime(director, directorTime);

                    //Need at least two frames in order to wait for the TimelineWindow to be updated ?
                    yield return(null);

                    yield return(null);

                    yield return(null);

                    //Unload texture because it may be overwritten
                    StreamingImageSequencePlugin.UnloadImageAndNotify(outputFilePath);
                    renderCapturer.CaptureToFile(outputFilePath, outputFormat);
                }
                Assert.IsTrue(File.Exists(outputFilePath));
                FileInfo fileInfo = new FileInfo(outputFilePath);

                imageFiles.Add(new WatchedFileInfo(fileName, fileInfo.Length));

                ++fileCounter;
                cancelled = EditorUtility.DisplayCancelableProgressBar(
                    "StreamingImageSequence", "Caching render results", ((float)(fileCounter - captureStartFrame) / numFiles));
            }

            if (!cancelled)
            {
                //Delete old files
                if (AssetDatabase.IsValidFolder(outputFolder))
                {
                    foreach (string oldFile in filesToDelete)
                    {
                        AssetDatabase.DeleteAsset(oldFile);
                    }
                }
                else
                {
                    foreach (string oldFile in filesToDelete)
                    {
                        File.Delete(oldFile);
                    }
                }
            }

            //Notify
            FolderContentsChangedNotifier.GetInstance().Notify(outputFolder);

            //Cleanup
            EditorUtility.ClearProgressBar();
            renderCapturer.EndCaptureV();
            ObjectUtility.Destroy(blitterGO);
            AssetDatabase.Refresh();
            renderCachePlayableAsset.Reload();;



            yield return(null);
        }
Ejemplo n.º 14
0
//----------------------------------------------------------------------------------------------------------------------
        public override void OnInspectorGUI()
        {
            ShortcutBinding useFrameShortcut
                = ShortcutManager.instance.GetShortcutBinding(SISEditorConstants.SHORTCUT_TOGGLE_FRAME_MARKER);
            bool prevUseFrame = m_assets[0].IsFrameUsed();
            bool useFrame     = EditorGUILayout.Toggle($"Use Frame ({useFrameShortcut})", prevUseFrame);

            if (useFrame != prevUseFrame)
            {
                //Set all selected objects
                foreach (FrameMarker m in m_assets)
                {
                    SetMarkerValueByContext(m, useFrame);
                }
            }

            if (1 == m_assets.Length)
            {
                SISPlayableFrame playableFrame = m_assets[0].GetOwner();
                string           prevNote      = playableFrame?.GetUserNote();
                DrawNoteGUI(prevNote);
            }
            else
            {
                int numSelectedAssets = m_assets.Length;
                Assert.IsTrue(numSelectedAssets > 1);
                SISPlayableFrame firstPlayableFrame = m_assets[0].GetOwner();
                //Check invalid PlayableFrame. Perhaps because of unsupported Duplicate operation ?
                if (null == firstPlayableFrame)
                {
                    return;
                }
                string prevNote = firstPlayableFrame.GetUserNote();
                for (int i = 1; i < numSelectedAssets; ++i)
                {
                    SISPlayableFrame playableFrame = m_assets[i].GetOwner();
                    if (playableFrame.GetUserNote() != prevNote)
                    {
                        prevNote = "<different notes>";
                    }
                }

                DrawNoteGUI(prevNote);
            }



            //Only show lock and edit for RenderCachePlayableAsset
            //[TODO-Sin: 2020-8-24]: Define capabilities in RenderCachePlayableAsset that defines what is visible
            foreach (FrameMarker frameMarker in m_assets)
            {
                SISPlayableFrame         playableFrame = frameMarker.GetOwner();
                RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset <RenderCachePlayableAsset>();
                if (null == playableAsset)
                {
                    return;
                }
            }

            GUILayout.Space(15);

            //m_assets only contain RenderCachePlayableAsset at this point
            ShortcutBinding lockAndEditShortcut
                = ShortcutManager.instance.GetShortcutBinding(SISEditorConstants.SHORTCUT_LOCK_AND_EDIT_FRAME);

            if (GUILayout.Button($"Lock and Edit ({lockAndEditShortcut})"))
            {
                foreach (FrameMarker frameMarker in m_assets)
                {
                    SISPlayableFrame         playableFrame = frameMarker.GetOwner();
                    RenderCachePlayableAsset playableAsset = playableFrame.GetTimelineClipAsset <RenderCachePlayableAsset>();
                    Assert.IsNotNull(playableAsset);
                    LockAndEditPlayableFrame(playableFrame, playableAsset);
                }
            }
        }