import datetime
import os
import shutil
import subprocess
import sys
import time
import traceback

import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as om

from keyframe_pro.keyframe_pro_client import KeyframeProClient


class MayaToKeyframePro:

    WINDOW_NAME = "MayaToKeyframeProWindow"
    WINDOW_TITLE = "Keyframe Pro"

    VERSION = "1.4.1"

    KEYFRAME_PRO_PATH = ""
    if sys.platform == "win32":
        KEYFRAME_PRO_PATH = "C:/Program Files/Keyframe Pro/bin/KeyframePro.exe"
    elif sys.platform == "darwin":
        KEYFRAME_PRO_PATH = "/Applications/KeyframePro.app/Contents/MacOS/KeyframePro"
    else:
        om.MGlobal.displayError("MayaToKeyframePro is not supported on the current platform ({0})".format(sys.platform))

    PORT = 18181

    SYNC_SCRIPT_NODE_NAME = "MayaToKeyframeProScriptNode"

    CACHED_TEMP_DIR_OPTION_VAR = "MayaToKeyframeProCachedTempDir"
    COLLAPSE_STATE_OPTION_VAR = "MayaToKeyframeProCollapseState"
    SYNC_OFFSET_OPTION_VAR = "MayaToKeyframeProSyncOffset"
    FROM_RANGE_START_OPTION_VAR = "MayaToKeyframeProFromRangeStart"

    WAIT_FOR_OPEN_DURATION = 1  # Seconds to sleep after trying to open the application

    BUTTON_COLOR_01 = (0.5, 0.5, 0.5)
    BUTTON_COLOR_02 = (0.361, 0.361, 0.361)

    SYNC_ACTIVE_COLOR = (0.0, 0.5, 0.0)

    kpro_client = None

    main_window = None
    sync_layout = None
    viewer_layout = None
    playblast_layout = None

    sync_from_range_start_cb = None
    sync_offset_ifg = None
    playblast_viewer_rbg = None

    @classmethod
    def open_keyframe_pro(cls, application_path=""):
        if not application_path:
            application_path = cls.KEYFRAME_PRO_PATH

        if not application_path:
            om.MGlobal.displayError("Keyframe Pro application path not set.")
        elif not os.path.exists(application_path):
            om.MGlobal.displayError("Keyframe Pro application path does not exist: {0}".format(application_path))
        else:
            try:
                subprocess.Popen(cls.KEYFRAME_PRO_PATH, shell=False, stdin=None, stdout=None, stderr=None)
            except:
                traceback.print_exc()
                om.MGlobal.displayError("Failed to open Keyframe Pro. See script editor for details.")

    @classmethod
    def is_initialized(cls, display_errors=True):
        if not cls.kpro_client:
            cls.kpro_client = KeyframeProClient()

        if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors):
            if cls.kpro_client.initialize():
                return True
        else:
            if display_errors:
                om.MGlobal.displayError("Connection failed. Application may be closed or the port may be in use ({0}).".format(cls.PORT))

        if display_errors:
            om.MGlobal.displayError("Failed to connect to Keyframe Pro. See script editor for details.")

        return False

    @classmethod
    def toggle_sync(cls):
        if not cls.sync_script_node_exists() and cls.is_initialized():
            cls.create_sync_script_node()
            if cls.sync_script_node_exists():
                cls.update_sync_time()
        else:
            cls.delete_sync_script_node()
            cls.kpro_client.disconnect()

        cls.update_sync_state()

    @classmethod
    def update_sync_time(cls):
            frame = cmds.currentTime(q=True) + cls.get_sync_offset()
            from_range_start = bool(cls.get_from_range_start())
            if cls.kpro_client.set_frame(frame, False, from_range_start) <= 0:
                cls.toggle_sync()

    @classmethod
    def set_viewer_layout(cls, layout):
        if cls.is_initialized():
            cls.kpro_client.set_viewer_layout(layout)

    @classmethod
    def swap_timelines(cls):
        if cls.is_initialized():
            a = cls.kpro_client.get_active_in_viewer(0)
            b = cls.kpro_client.get_active_in_viewer(1)
            if b:
                cls.kpro_client.set_active_in_viewer(b["id"], 0)
            if a:
                cls.kpro_client.set_active_in_viewer(a["id"], 1)

    @classmethod
    def playblast(cls):

        format = cls.get_option_var("playblastFormat", "avi")
        ext = ""
        if format == "avi":
            ext = "avi"
        elif format == "qt" or format == "avfoundation":
            ext = "mov"
        elif format == "image":
            ext = ""
        else:
            om.MGlobal.displayError("Unsupported playblast format: {0}".format(format))
            return

        temp_dir = cls.get_temp_dir()
        if not temp_dir:
            om.MGlobal.displayError("Failed to get temp directory from Keyframe Pro. See script editor for details.")
            return

        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)

        name = "blast"
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        if format == "image":
            file_path = "{0}/{1}_{2}".format(temp_dir, name, timestamp)
        else:
            file_path = "{0}/{1}_{2}.{3}".format(temp_dir, name, timestamp, ext)

        clear_cache = cls.get_option_var("playblastClearCache", True)
        show_ornaments = cls.get_option_var("playblastShowOrnaments", False)
        compression = cls.get_option_var("playblastCompression", "none")
        quality = cls.get_option_var("playblastQuality", 70)
        percent = cls.get_option_var("playblastScale", 0.5) * 100
        padding = cls.get_option_var("playblastPadding", 4)
        display_source_size = cls.get_option_var("playblastDisplaySizeSource", 1)
        playblast_width = cls.get_option_var("playblastWidth", 720)
        playblast_height = cls.get_option_var("playblastHeight", 480)

        args = {"format": format,
                "clearCache": clear_cache,
                "viewer": False,
                "showOrnaments": show_ornaments,
                "fp": padding,
                "percent": percent,
                "compression": compression,
                "quality": quality,
                "filename": file_path
                }

        if display_source_size == 2:
            args["widthHeight"] = [cmds.getAttr("defaultResolution.w"), cmds.getAttr("defaultResolution.h")]
        elif display_source_size == 3:
            args["widthHeight"] = [playblast_width, playblast_height]

        playback_slider = mel.eval("$tempVar = $gPlayBackSlider")
        if(cmds.timeControl(playback_slider, q=True, rv=True)):
            range = cmds.timeControl(playback_slider, q=True, ra=True)
            args["startTime"] = range[0]
            args["endTime"] = range[1]

        sound = cmds.timeControl(playback_slider, q=True, sound=True)
        if sound:
            args["sound"] = sound

        temp_path = cmds.playblast(**args)
        if temp_path:
            file_path = temp_path
            
        if not os.path.exists(file_path):
            om.MGlobal.displayError("Playblast file does not exist. See script editor for details.")
            return
            
        # Open in viewer
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, query=True, select=True) - 1
        if viewer_index <= 1:
            if not cls.is_initialized(False):
                cls.open_keyframe_pro()
                time.sleep(cls.WAIT_FOR_OPEN_DURATION)

                if not cls.is_initialized():
                    om.MGlobal.displayError("Failed to open in viewer. See script editor for details.")
                    return

            if viewer_index >= 0 and viewer_index <= 1:
                # On import, source may be loaded into A. Restore current A if source is to be in B
                source_in_a = None
                if viewer_index > 0:
                    source_in_a = cls.kpro_client.get_active_in_viewer(0)

                # Swap
                source = cls.kpro_client.import_file(file_path)
                if source:
                    cls.kpro_client.set_active_in_viewer(source["id"], viewer_index)
                if source_in_a:
                    cls.kpro_client.set_active_in_viewer(source_in_a["id"], 0)

            autoplay = cls.kpro_client.is_autoplay()
            if(autoplay):
                cls.kpro_client.set_playing(autoplay)

    @classmethod
    def get_option_var(cls, name, default):
        if cmds.optionVar(exists=name):
            return cmds.optionVar(q=name)
        else:
            return default

    @classmethod
    def open_temp_dir(cls):
        temp_dir = cls.get_temp_dir()
        if temp_dir:
            if sys.platform == "win32":
                os.startfile(temp_dir, 'explore')
            elif sys.platform == "darwin":
                subprocess.Popen(["open", temp_dir])
            else:
                om.MGlobal.displayError("Open temp dir is not supported on the current platform ({0})".format(sys.platform))
        else:
            om.MGlobal.displayError("Failed to get temp directory from Keyframe Pro. See script editor for details.")

    @classmethod
    def clear_temp_dir(cls):
        result = cmds.confirmDialog(title='Confirm',
                                    message='Clear temporary directory?',
                                    button=['Yes', 'No'],
                                    defaultButton='Yes',
                                    cancelButton='No',
                                    dismissString='No')
        if result == "Yes":
            temp_dir = cls.get_temp_dir()
            if temp_dir:
                errors_occurred = False

                for the_file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, the_file)
                    try:
                        if os.path.isfile(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except:
                        om.MGlobal.displayWarning("Failed to remove file: {0}".format(file_path))
                        om.MGlobal.displayWarning("File may be open in an application")
                        errors_occurred = True

                if errors_occurred:
                    om.MGlobal.displayWarning("Unable to remove all files. See script editor for details.")
                else:
                    om.MGlobal.displayInfo("Temporary directory cleared: {0}".format(temp_dir))
                    
            else:
                om.MGlobal.displayError("Failed to get temp directory from Keyframe Pro. See script editor for details.")

    @classmethod
    def get_temp_dir(cls):
        if cls.is_initialized(display_errors=False):
            config = cls.kpro_client.get_config()
            if config:
                cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config["temp_dir"]])
                return config["temp_dir"]

        temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, "")
        if not temp_dir:
            cls.open_keyframe_pro()
            time.sleep(cls.WAIT_FOR_OPEN_DURATION)
            
            if cls.is_initialized(display_errors=False):
                config = cls.kpro_client.get_config()
                if config:
                    cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config["temp_dir"]])
                    return config["temp_dir"]

        return temp_dir

    @classmethod
    def sync_script_node_exists(cls):
        return cmds.objExists(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def create_sync_script_node(cls):
        if not cls.sync_script_node_exists():
            cmds.scriptNode(scriptType=7,
                            beforeScript="try: MayaToKeyframePro.update_sync_time()\nexcept: pass",
                            name=cls.SYNC_SCRIPT_NODE_NAME,
                            sourceType="python")

    @classmethod
    def delete_sync_script_node(cls):
        if cls.sync_script_node_exists():
            cmds.delete(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def get_sync_offset(cls):
        if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR):
            return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR)
        else:
            return 0

    @classmethod
    def set_sync_offset(cls, value):
        cmds.intFieldGrp(cls.sync_offset_ifg, e=True, value1=value)
        cmds.optionVar(iv=[cls.SYNC_OFFSET_OPTION_VAR, value])
        if (cls.sync_script_node_exists()):
            cls.update_sync_time()

    @classmethod
    def sync_offset_to_current(cls):
        cls.set_sync_offset(-cmds.currentTime(q=True) + 1)

    @classmethod
    def sync_offset_changed(cls):
        cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True, value1=True))

    @classmethod
    def get_from_range_start(cls):
        if cmds.optionVar(exists=cls.FROM_RANGE_START_OPTION_VAR):
            return cmds.optionVar(q=cls.FROM_RANGE_START_OPTION_VAR)
        else:
            return 1

    @classmethod
    def update_from_range_start(cls):
        value = cmds.checkBox(cls.sync_from_range_start_cb, q=True, value=True)
        cmds.optionVar(iv=[cls.FROM_RANGE_START_OPTION_VAR, value])

        if cls.sync_script_node_exists():
            cls.update_sync_time()

    @classmethod
    def get_collapse_state(cls):
        if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR):
            collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR)
            if len(collapse_state) == 3:
                for value in collapse_state:
                    if value < 0 or value > 1:
                        return [0, 1, 1]

                return collapse_state

        return [0, 1, 1]

    @classmethod
    def update_collapse_state(cls):
        cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR)
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            collapse = cmds.frameLayout(layout, q=True, cl=True)
            cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse])

    @classmethod
    def display(cls):

        if(KeyframeProClient.API_VERSION != MayaToKeyframePro.VERSION):
            om.MGlobal.displayInfo("The Keyframe Pro Client API ({0}) and the MayaToKeyframePro script ({1}) versions do not match.".format(KeyframeProClient.API_VERSION, MayaToKeyframePro.VERSION))
            om.MGlobal.displayInfo("Please download and update to the latest versions: http://zurbrigg.com/keyframe-pro")
            om.MGlobal.displayError("Client API Version Mismatch (see the script editor for more details)")
            return

        if cmds.window(cls.WINDOW_NAME, exists=True):
            cmds.deleteUI(cls.WINDOW_NAME, window=True)

        collapse_state = cls.get_collapse_state()

        # ---------------------------------------------------------------------
        # Main layout
        # ---------------------------------------------------------------------
        cls.main_window = cmds.window(cls.WINDOW_NAME, title=cls.WINDOW_TITLE, s=True, tlb=False, rtf=True, mnb=False, mxb=False)
        main_layout = cmds.formLayout(parent=cls.main_window)

        cls.sync_layout = cmds.frameLayout(parent=main_layout,
                                           label="Sync", collapsable=True,
                                           cl=collapse_state[0],
                                           cc='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
                                           ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        sync_form_layout = cmds.formLayout(parent=cls.sync_layout)

        cls.viewer_layout = cmds.frameLayout(parent=main_layout,
                                             label="Viewer",
                                             collapsable=True,
                                             cl=collapse_state[1],
                                             cc='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
                                             ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        viewer_form_layout = cmds.formLayout(parent=cls.viewer_layout)

        cls.playblast_layout = cmds.frameLayout(parent=main_layout,
                                                label="Playblast",
                                                collapsable=True,
                                                cl=collapse_state[2],
                                                cc='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
                                                ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        playblast_form_layout = cmds.formLayout(parent=cls.playblast_layout)

        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "top", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "left", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "right", 0))

        cmds.formLayout(main_layout, e=True, ac=(cls.viewer_layout, "top", 0, cls.sync_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, "left", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, "right", 0))

        cmds.formLayout(main_layout, e=True, ac=(cls.playblast_layout, "top", 0, cls.viewer_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout, "left", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout, "right", 0))

        # ---------------------------------------------------------------------
        # Sync layout
        # ---------------------------------------------------------------------
        cls.sync_offset_ifg = cmds.intFieldGrp(label="Offset: ",
                                               value1=MayaToKeyframePro.get_sync_offset(),
                                               columnWidth2=(40, 48),
                                               cl2=("left", "right"),
                                               cc="MayaToKeyframePro.sync_offset_changed()",
                                               parent=sync_form_layout)

        cls.sync_from_range_start_cb = cmds.checkBox(label="From Range Start",
                                                     value=MayaToKeyframePro.get_from_range_start(),
                                                     cc="MayaToKeyframePro.update_from_range_start()",
                                                     parent=sync_form_layout)

        sync_offset_to_current_btn = cmds.button(label="Current",
                                                 bgc=cls.BUTTON_COLOR_01,
                                                 c="MayaToKeyframePro.sync_offset_to_current()",
                                                 parent=sync_form_layout)

        reset_sync_offset_btn = cmds.button(label="  Reset  ",
                                            bgc=cls.BUTTON_COLOR_01,
                                            c="MayaToKeyframePro.set_sync_offset(0)",
                                            parent=sync_form_layout)

        cls.sync_btn = cmds.button(label="SYNC", c="MayaToKeyframePro.toggle_sync()", parent=sync_form_layout)

        top_offset = 1
        bottom_offset = 4
        left_position = 1
        right_position = 99
        spacing = 2
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_offset_ifg, "top", top_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_offset_ifg, "left", 0, left_position))

        cmds.formLayout(sync_form_layout, e=True, af=(sync_offset_to_current_btn, "top", top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(sync_offset_to_current_btn, "left", 0, cls.sync_offset_ifg))

        cmds.formLayout(sync_form_layout, e=True, af=(reset_sync_offset_btn, "top", top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(reset_sync_offset_btn, "left", spacing, sync_offset_to_current_btn))

        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_from_range_start_cb, "top", top_offset, sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_from_range_start_cb, "left", 0, left_position))

        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_btn, "top", 2 * spacing, cls.sync_from_range_start_cb))
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_btn, "bottom", bottom_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, "left", 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, "right", 0, right_position))

        # ---------------------------------------------------------------------
        # Viewer layout
        # ---------------------------------------------------------------------
        single_viewer_btn = cmds.button(label="Single",
                                        bgc=cls.BUTTON_COLOR_01,
                                        c="MayaToKeyframePro.set_viewer_layout('single')",
                                        parent=viewer_form_layout)

        hori_viewer_btn = cmds.button(label="Horizontal",
                                      bgc=cls.BUTTON_COLOR_01,
                                      c="MayaToKeyframePro.set_viewer_layout('horizontal')",
                                      parent=viewer_form_layout)

        vert_viewer_btn = cmds.button(label=" Vertical ",
                                      bgc=cls.BUTTON_COLOR_01,
                                      c="MayaToKeyframePro.set_viewer_layout('vertical')",
                                      parent=viewer_form_layout)

        swap_timelines_btn = cmds.button(label="Swap Timelines",
                                         bgc=cls.BUTTON_COLOR_01,
                                         c="MayaToKeyframePro.swap_timelines()",
                                         parent=viewer_form_layout)

        cmds.formLayout(viewer_form_layout, e=True, af=(single_viewer_btn, "top", top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn, "left", 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn, "right", 0, 38))

        cmds.formLayout(viewer_form_layout, e=True, af=(hori_viewer_btn, "top", top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(hori_viewer_btn, "left", spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(hori_viewer_btn, "right", 0, 68))

        cmds.formLayout(viewer_form_layout, e=True, af=(vert_viewer_btn, "top", top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(vert_viewer_btn, "left", spacing, hori_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(vert_viewer_btn, "right", 0, right_position))

        cmds.formLayout(viewer_form_layout, e=True, ac=(swap_timelines_btn, "top", spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, af=(swap_timelines_btn, "bottom", bottom_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn, "left", 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn, "right", 0, right_position))

        # ---------------------------------------------------------------------
        # Playblast layout
        # ---------------------------------------------------------------------
        cls.playblast_viewer_rbg = cmds.radioButtonGrp(label='Open in Viewer:   ',
                                                       labelArray3=['A', 'B', 'None'],
                                                       numberOfRadioButtons=3,
                                                       select=1,
                                                       cw4=(100, 40, 40, 40),
                                                       cl4=("left", "left", "left", "left"),
                                                       parent=playblast_form_layout)

        playblast_btn = cmds.button(label="PLAYBLAST",
                                    bgc=cls.BUTTON_COLOR_01,
                                    c="MayaToKeyframePro.playblast()",
                                    parent=playblast_form_layout)

        open_temp_dir_btn = cmds.button(label="Open Temp Folder",
                                        bgc=cls.BUTTON_COLOR_01,
                                        c="MayaToKeyframePro.open_temp_dir()",
                                        parent=playblast_form_layout)

        clear_temp_dir_btn = cmds.button(label="Clear Temp Folder",
                                         bgc=cls.BUTTON_COLOR_01,
                                         c="MayaToKeyframePro.clear_temp_dir()",
                                         parent=playblast_form_layout)

        version_label = cmds.text(label="v{0}".format(cls.VERSION), align="right")

        cmds.formLayout(playblast_form_layout, e=True, af=(cls.playblast_viewer_rbg, "top", top_offset))
        cmds.formLayout(playblast_form_layout, e=True, ap=(cls.playblast_viewer_rbg, "left", 0, left_position))

        cmds.formLayout(playblast_form_layout, e=True, ac=(playblast_btn, "top", spacing, cls.playblast_viewer_rbg))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn, "left", 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn, "right", 0, right_position))

        cmds.formLayout(playblast_form_layout, e=True, ac=(open_temp_dir_btn, "top", spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(open_temp_dir_btn, "left", 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(open_temp_dir_btn, "right", 1, 50))

        cmds.formLayout(playblast_form_layout, e=True, ac=(clear_temp_dir_btn, "top", spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(clear_temp_dir_btn, "left", 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ap=(clear_temp_dir_btn, "right", 0, right_position))

        cmds.formLayout(playblast_form_layout, e=True, ac=(version_label, "top", spacing, open_temp_dir_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(version_label, "right", 0, right_position))

        # ---------------------------------------------------------------------
        # Update and show
        # ---------------------------------------------------------------------
        cls.update_sync_state()
        cls.on_collapse_changed()
        cmds.setFocus(cls.sync_btn)

        cmds.showWindow(cls.main_window)

    @classmethod
    def on_collapse_changed(cls):
        total_height = 0
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            total_height += cmds.frameLayout(layout, q=True, h=True)

        cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height)
        cls.update_collapse_state()

    @classmethod
    def update_sync_state(cls):
        if cls.sync_script_node_exists():
            cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR, label="SYNCED")
        else:
            cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01, label="SYNC")


if __name__ == "__main__":
    MayaToKeyframePro.display()

677246bdec85a68e0deba2529115fd3b2100875e
<mask token>


class MayaToKeyframePro:
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    if sys.platform == 'win32':
        KEYFRAME_PRO_PATH = 'C:/Program Files/Keyframe Pro/bin/KeyframePro.exe'
    elif sys.platform == 'darwin':
        KEYFRAME_PRO_PATH = (
            '/Applications/KeyframePro.app/Contents/MacOS/KeyframePro')
    else:
        om.MGlobal.displayError(
            'MayaToKeyframePro is not supported on the current platform ({0})'
            .format(sys.platform))
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    @classmethod
    def is_initialized(cls, display_errors=True):
        if not cls.kpro_client:
            cls.kpro_client = KeyframeProClient()
        if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors
            ):
            if cls.kpro_client.initialize():
                return True
        elif display_errors:
            om.MGlobal.displayError(
                'Connection failed. Application may be closed or the port may be in use ({0}).'
                .format(cls.PORT))
        if display_errors:
            om.MGlobal.displayError(
                'Failed to connect to Keyframe Pro. See script editor for details.'
                )
        return False
    <mask token>
    <mask token>

    @classmethod
    def set_viewer_layout(cls, layout):
        if cls.is_initialized():
            cls.kpro_client.set_viewer_layout(layout)
    <mask token>

    @classmethod
    def playblast(cls):
        format = cls.get_option_var('playblastFormat', 'avi')
        ext = ''
        if format == 'avi':
            ext = 'avi'
        elif format == 'qt' or format == 'avfoundation':
            ext = 'mov'
        elif format == 'image':
            ext = ''
        else:
            om.MGlobal.displayError('Unsupported playblast format: {0}'.
                format(format))
            return
        temp_dir = cls.get_temp_dir()
        if not temp_dir:
            om.MGlobal.displayError(
                'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                )
            return
        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)
        name = 'blast'
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        if format == 'image':
            file_path = '{0}/{1}_{2}'.format(temp_dir, name, timestamp)
        else:
            file_path = '{0}/{1}_{2}.{3}'.format(temp_dir, name, timestamp, ext
                )
        clear_cache = cls.get_option_var('playblastClearCache', True)
        show_ornaments = cls.get_option_var('playblastShowOrnaments', False)
        compression = cls.get_option_var('playblastCompression', 'none')
        quality = cls.get_option_var('playblastQuality', 70)
        percent = cls.get_option_var('playblastScale', 0.5) * 100
        padding = cls.get_option_var('playblastPadding', 4)
        display_source_size = cls.get_option_var('playblastDisplaySizeSource',
            1)
        playblast_width = cls.get_option_var('playblastWidth', 720)
        playblast_height = cls.get_option_var('playblastHeight', 480)
        args = {'format': format, 'clearCache': clear_cache, 'viewer': 
            False, 'showOrnaments': show_ornaments, 'fp': padding,
            'percent': percent, 'compression': compression, 'quality':
            quality, 'filename': file_path}
        if display_source_size == 2:
            args['widthHeight'] = [cmds.getAttr('defaultResolution.w'),
                cmds.getAttr('defaultResolution.h')]
        elif display_source_size == 3:
            args['widthHeight'] = [playblast_width, playblast_height]
        playback_slider = mel.eval('$tempVar = $gPlayBackSlider')
        if cmds.timeControl(playback_slider, q=True, rv=True):
            range = cmds.timeControl(playback_slider, q=True, ra=True)
            args['startTime'] = range[0]
            args['endTime'] = range[1]
        sound = cmds.timeControl(playback_slider, q=True, sound=True)
        if sound:
            args['sound'] = sound
        temp_path = cmds.playblast(**args)
        if temp_path:
            file_path = temp_path
        if not os.path.exists(file_path):
            om.MGlobal.displayError(
                'Playblast file does not exist. See script editor for details.'
                )
            return
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, query=
            True, select=True) - 1
        if viewer_index <= 1:
            if not cls.is_initialized(False):
                cls.open_keyframe_pro()
                time.sleep(cls.WAIT_FOR_OPEN_DURATION)
                if not cls.is_initialized():
                    om.MGlobal.displayError(
                        'Failed to open in viewer. See script editor for details.'
                        )
                    return
            if viewer_index >= 0 and viewer_index <= 1:
                source_in_a = None
                if viewer_index > 0:
                    source_in_a = cls.kpro_client.get_active_in_viewer(0)
                source = cls.kpro_client.import_file(file_path)
                if source:
                    cls.kpro_client.set_active_in_viewer(source['id'],
                        viewer_index)
                if source_in_a:
                    cls.kpro_client.set_active_in_viewer(source_in_a['id'], 0)
            autoplay = cls.kpro_client.is_autoplay()
            if autoplay:
                cls.kpro_client.set_playing(autoplay)
    <mask token>
    <mask token>

    @classmethod
    def clear_temp_dir(cls):
        result = cmds.confirmDialog(title='Confirm', message=
            'Clear temporary directory?', button=['Yes', 'No'],
            defaultButton='Yes', cancelButton='No', dismissString='No')
        if result == 'Yes':
            temp_dir = cls.get_temp_dir()
            if temp_dir:
                errors_occurred = False
                for the_file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, the_file)
                    try:
                        if os.path.isfile(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except:
                        om.MGlobal.displayWarning('Failed to remove file: {0}'
                            .format(file_path))
                        om.MGlobal.displayWarning(
                            'File may be open in an application')
                        errors_occurred = True
                if errors_occurred:
                    om.MGlobal.displayWarning(
                        'Unable to remove all files. See script editor for details.'
                        )
                else:
                    om.MGlobal.displayInfo('Temporary directory cleared: {0}'
                        .format(temp_dir))
            else:
                om.MGlobal.displayError(
                    'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                    )

    @classmethod
    def get_temp_dir(cls):
        if cls.is_initialized(display_errors=False):
            config = cls.kpro_client.get_config()
            if config:
                cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config[
                    'temp_dir']])
                return config['temp_dir']
        temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, '')
        if not temp_dir:
            cls.open_keyframe_pro()
            time.sleep(cls.WAIT_FOR_OPEN_DURATION)
            if cls.is_initialized(display_errors=False):
                config = cls.kpro_client.get_config()
                if config:
                    cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR,
                        config['temp_dir']])
                    return config['temp_dir']
        return temp_dir
    <mask token>

    @classmethod
    def create_sync_script_node(cls):
        if not cls.sync_script_node_exists():
            cmds.scriptNode(scriptType=7, beforeScript=
                """try: MayaToKeyframePro.update_sync_time()
except: pass""",
                name=cls.SYNC_SCRIPT_NODE_NAME, sourceType='python')

    @classmethod
    def delete_sync_script_node(cls):
        if cls.sync_script_node_exists():
            cmds.delete(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def get_sync_offset(cls):
        if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR):
            return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR)
        else:
            return 0
    <mask token>

    @classmethod
    def sync_offset_to_current(cls):
        cls.set_sync_offset(-cmds.currentTime(q=True) + 1)

    @classmethod
    def sync_offset_changed(cls):
        cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True,
            value1=True))
    <mask token>
    <mask token>

    @classmethod
    def get_collapse_state(cls):
        if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR):
            collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR)
            if len(collapse_state) == 3:
                for value in collapse_state:
                    if value < 0 or value > 1:
                        return [0, 1, 1]
                return collapse_state
        return [0, 1, 1]

    @classmethod
    def update_collapse_state(cls):
        cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR)
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            collapse = cmds.frameLayout(layout, q=True, cl=True)
            cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse])
    <mask token>

    @classmethod
    def on_collapse_changed(cls):
        total_height = 0
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            total_height += cmds.frameLayout(layout, q=True, h=True)
        cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height)
        cls.update_collapse_state()

    @classmethod
    def update_sync_state(cls):
        if cls.sync_script_node_exists():
            cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR,
                label='SYNCED')
        else:
            cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01,
                label='SYNC')


<mask token>

<mask token>


class MayaToKeyframePro:
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    if sys.platform == 'win32':
        KEYFRAME_PRO_PATH = 'C:/Program Files/Keyframe Pro/bin/KeyframePro.exe'
    elif sys.platform == 'darwin':
        KEYFRAME_PRO_PATH = (
            '/Applications/KeyframePro.app/Contents/MacOS/KeyframePro')
    else:
        om.MGlobal.displayError(
            'MayaToKeyframePro is not supported on the current platform ({0})'
            .format(sys.platform))
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    @classmethod
    def is_initialized(cls, display_errors=True):
        if not cls.kpro_client:
            cls.kpro_client = KeyframeProClient()
        if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors
            ):
            if cls.kpro_client.initialize():
                return True
        elif display_errors:
            om.MGlobal.displayError(
                'Connection failed. Application may be closed or the port may be in use ({0}).'
                .format(cls.PORT))
        if display_errors:
            om.MGlobal.displayError(
                'Failed to connect to Keyframe Pro. See script editor for details.'
                )
        return False
    <mask token>
    <mask token>

    @classmethod
    def set_viewer_layout(cls, layout):
        if cls.is_initialized():
            cls.kpro_client.set_viewer_layout(layout)
    <mask token>

    @classmethod
    def playblast(cls):
        format = cls.get_option_var('playblastFormat', 'avi')
        ext = ''
        if format == 'avi':
            ext = 'avi'
        elif format == 'qt' or format == 'avfoundation':
            ext = 'mov'
        elif format == 'image':
            ext = ''
        else:
            om.MGlobal.displayError('Unsupported playblast format: {0}'.
                format(format))
            return
        temp_dir = cls.get_temp_dir()
        if not temp_dir:
            om.MGlobal.displayError(
                'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                )
            return
        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)
        name = 'blast'
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        if format == 'image':
            file_path = '{0}/{1}_{2}'.format(temp_dir, name, timestamp)
        else:
            file_path = '{0}/{1}_{2}.{3}'.format(temp_dir, name, timestamp, ext
                )
        clear_cache = cls.get_option_var('playblastClearCache', True)
        show_ornaments = cls.get_option_var('playblastShowOrnaments', False)
        compression = cls.get_option_var('playblastCompression', 'none')
        quality = cls.get_option_var('playblastQuality', 70)
        percent = cls.get_option_var('playblastScale', 0.5) * 100
        padding = cls.get_option_var('playblastPadding', 4)
        display_source_size = cls.get_option_var('playblastDisplaySizeSource',
            1)
        playblast_width = cls.get_option_var('playblastWidth', 720)
        playblast_height = cls.get_option_var('playblastHeight', 480)
        args = {'format': format, 'clearCache': clear_cache, 'viewer': 
            False, 'showOrnaments': show_ornaments, 'fp': padding,
            'percent': percent, 'compression': compression, 'quality':
            quality, 'filename': file_path}
        if display_source_size == 2:
            args['widthHeight'] = [cmds.getAttr('defaultResolution.w'),
                cmds.getAttr('defaultResolution.h')]
        elif display_source_size == 3:
            args['widthHeight'] = [playblast_width, playblast_height]
        playback_slider = mel.eval('$tempVar = $gPlayBackSlider')
        if cmds.timeControl(playback_slider, q=True, rv=True):
            range = cmds.timeControl(playback_slider, q=True, ra=True)
            args['startTime'] = range[0]
            args['endTime'] = range[1]
        sound = cmds.timeControl(playback_slider, q=True, sound=True)
        if sound:
            args['sound'] = sound
        temp_path = cmds.playblast(**args)
        if temp_path:
            file_path = temp_path
        if not os.path.exists(file_path):
            om.MGlobal.displayError(
                'Playblast file does not exist. See script editor for details.'
                )
            return
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, query=
            True, select=True) - 1
        if viewer_index <= 1:
            if not cls.is_initialized(False):
                cls.open_keyframe_pro()
                time.sleep(cls.WAIT_FOR_OPEN_DURATION)
                if not cls.is_initialized():
                    om.MGlobal.displayError(
                        'Failed to open in viewer. See script editor for details.'
                        )
                    return
            if viewer_index >= 0 and viewer_index <= 1:
                source_in_a = None
                if viewer_index > 0:
                    source_in_a = cls.kpro_client.get_active_in_viewer(0)
                source = cls.kpro_client.import_file(file_path)
                if source:
                    cls.kpro_client.set_active_in_viewer(source['id'],
                        viewer_index)
                if source_in_a:
                    cls.kpro_client.set_active_in_viewer(source_in_a['id'], 0)
            autoplay = cls.kpro_client.is_autoplay()
            if autoplay:
                cls.kpro_client.set_playing(autoplay)
    <mask token>
    <mask token>

    @classmethod
    def clear_temp_dir(cls):
        result = cmds.confirmDialog(title='Confirm', message=
            'Clear temporary directory?', button=['Yes', 'No'],
            defaultButton='Yes', cancelButton='No', dismissString='No')
        if result == 'Yes':
            temp_dir = cls.get_temp_dir()
            if temp_dir:
                errors_occurred = False
                for the_file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, the_file)
                    try:
                        if os.path.isfile(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except:
                        om.MGlobal.displayWarning('Failed to remove file: {0}'
                            .format(file_path))
                        om.MGlobal.displayWarning(
                            'File may be open in an application')
                        errors_occurred = True
                if errors_occurred:
                    om.MGlobal.displayWarning(
                        'Unable to remove all files. See script editor for details.'
                        )
                else:
                    om.MGlobal.displayInfo('Temporary directory cleared: {0}'
                        .format(temp_dir))
            else:
                om.MGlobal.displayError(
                    'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                    )

    @classmethod
    def get_temp_dir(cls):
        if cls.is_initialized(display_errors=False):
            config = cls.kpro_client.get_config()
            if config:
                cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config[
                    'temp_dir']])
                return config['temp_dir']
        temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, '')
        if not temp_dir:
            cls.open_keyframe_pro()
            time.sleep(cls.WAIT_FOR_OPEN_DURATION)
            if cls.is_initialized(display_errors=False):
                config = cls.kpro_client.get_config()
                if config:
                    cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR,
                        config['temp_dir']])
                    return config['temp_dir']
        return temp_dir
    <mask token>

    @classmethod
    def create_sync_script_node(cls):
        if not cls.sync_script_node_exists():
            cmds.scriptNode(scriptType=7, beforeScript=
                """try: MayaToKeyframePro.update_sync_time()
except: pass""",
                name=cls.SYNC_SCRIPT_NODE_NAME, sourceType='python')

    @classmethod
    def delete_sync_script_node(cls):
        if cls.sync_script_node_exists():
            cmds.delete(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def get_sync_offset(cls):
        if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR):
            return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR)
        else:
            return 0
    <mask token>

    @classmethod
    def sync_offset_to_current(cls):
        cls.set_sync_offset(-cmds.currentTime(q=True) + 1)

    @classmethod
    def sync_offset_changed(cls):
        cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True,
            value1=True))

    @classmethod
    def get_from_range_start(cls):
        if cmds.optionVar(exists=cls.FROM_RANGE_START_OPTION_VAR):
            return cmds.optionVar(q=cls.FROM_RANGE_START_OPTION_VAR)
        else:
            return 1
    <mask token>

    @classmethod
    def get_collapse_state(cls):
        if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR):
            collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR)
            if len(collapse_state) == 3:
                for value in collapse_state:
                    if value < 0 or value > 1:
                        return [0, 1, 1]
                return collapse_state
        return [0, 1, 1]

    @classmethod
    def update_collapse_state(cls):
        cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR)
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            collapse = cmds.frameLayout(layout, q=True, cl=True)
            cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse])

    @classmethod
    def display(cls):
        if KeyframeProClient.API_VERSION != MayaToKeyframePro.VERSION:
            om.MGlobal.displayInfo(
                'The Keyframe Pro Client API ({0}) and the MayaToKeyframePro script ({1}) versions do not match.'
                .format(KeyframeProClient.API_VERSION, MayaToKeyframePro.
                VERSION))
            om.MGlobal.displayInfo(
                'Please download and update to the latest versions: http://zurbrigg.com/keyframe-pro'
                )
            om.MGlobal.displayError(
                'Client API Version Mismatch (see the script editor for more details)'
                )
            return
        if cmds.window(cls.WINDOW_NAME, exists=True):
            cmds.deleteUI(cls.WINDOW_NAME, window=True)
        collapse_state = cls.get_collapse_state()
        cls.main_window = cmds.window(cls.WINDOW_NAME, title=cls.
            WINDOW_TITLE, s=True, tlb=False, rtf=True, mnb=False, mxb=False)
        main_layout = cmds.formLayout(parent=cls.main_window)
        cls.sync_layout = cmds.frameLayout(parent=main_layout, label='Sync',
            collapsable=True, cl=collapse_state[0], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        sync_form_layout = cmds.formLayout(parent=cls.sync_layout)
        cls.viewer_layout = cmds.frameLayout(parent=main_layout, label=
            'Viewer', collapsable=True, cl=collapse_state[1], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        viewer_form_layout = cmds.formLayout(parent=cls.viewer_layout)
        cls.playblast_layout = cmds.frameLayout(parent=main_layout, label=
            'Playblast', collapsable=True, cl=collapse_state[2], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        playblast_form_layout = cmds.formLayout(parent=cls.playblast_layout)
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'top', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'right', 0))
        cmds.formLayout(main_layout, e=True, ac=(cls.viewer_layout, 'top', 
            0, cls.sync_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, 'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, 'right', 0)
            )
        cmds.formLayout(main_layout, e=True, ac=(cls.playblast_layout,
            'top', 0, cls.viewer_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout,
            'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout,
            'right', 0))
        cls.sync_offset_ifg = cmds.intFieldGrp(label='Offset: ', value1=
            MayaToKeyframePro.get_sync_offset(), columnWidth2=(40, 48), cl2
            =('left', 'right'), cc=
            'MayaToKeyframePro.sync_offset_changed()', parent=sync_form_layout)
        cls.sync_from_range_start_cb = cmds.checkBox(label=
            'From Range Start', value=MayaToKeyframePro.
            get_from_range_start(), cc=
            'MayaToKeyframePro.update_from_range_start()', parent=
            sync_form_layout)
        sync_offset_to_current_btn = cmds.button(label='Current', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.sync_offset_to_current()',
            parent=sync_form_layout)
        reset_sync_offset_btn = cmds.button(label='  Reset  ', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.set_sync_offset(0)',
            parent=sync_form_layout)
        cls.sync_btn = cmds.button(label='SYNC', c=
            'MayaToKeyframePro.toggle_sync()', parent=sync_form_layout)
        top_offset = 1
        bottom_offset = 4
        left_position = 1
        right_position = 99
        spacing = 2
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_offset_ifg,
            'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_offset_ifg,
            'left', 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, af=(
            sync_offset_to_current_btn, 'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(
            sync_offset_to_current_btn, 'left', 0, cls.sync_offset_ifg))
        cmds.formLayout(sync_form_layout, e=True, af=(reset_sync_offset_btn,
            'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(reset_sync_offset_btn,
            'left', spacing, sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ac=(cls.
            sync_from_range_start_cb, 'top', top_offset,
            sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.
            sync_from_range_start_cb, 'left', 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_btn, 'top', 
            2 * spacing, cls.sync_from_range_start_cb))
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_btn,
            'bottom', bottom_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, 'left',
            0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, 'right',
            0, right_position))
        single_viewer_btn = cmds.button(label='Single', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('single')", parent=
            viewer_form_layout)
        hori_viewer_btn = cmds.button(label='Horizontal', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('horizontal')", parent=
            viewer_form_layout)
        vert_viewer_btn = cmds.button(label=' Vertical ', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('vertical')", parent=
            viewer_form_layout)
        swap_timelines_btn = cmds.button(label='Swap Timelines', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.swap_timelines()', parent
            =viewer_form_layout)
        cmds.formLayout(viewer_form_layout, e=True, af=(single_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn,
            'left', 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn,
            'right', 0, 38))
        cmds.formLayout(viewer_form_layout, e=True, af=(hori_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(hori_viewer_btn,
            'left', spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(hori_viewer_btn,
            'right', 0, 68))
        cmds.formLayout(viewer_form_layout, e=True, af=(vert_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(vert_viewer_btn,
            'left', spacing, hori_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(vert_viewer_btn,
            'right', 0, right_position))
        cmds.formLayout(viewer_form_layout, e=True, ac=(swap_timelines_btn,
            'top', spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, af=(swap_timelines_btn,
            'bottom', bottom_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn,
            'left', 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn,
            'right', 0, right_position))
        cls.playblast_viewer_rbg = cmds.radioButtonGrp(label=
            'Open in Viewer:   ', labelArray3=['A', 'B', 'None'],
            numberOfRadioButtons=3, select=1, cw4=(100, 40, 40, 40), cl4=(
            'left', 'left', 'left', 'left'), parent=playblast_form_layout)
        playblast_btn = cmds.button(label='PLAYBLAST', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.playblast()', parent=
            playblast_form_layout)
        open_temp_dir_btn = cmds.button(label='Open Temp Folder', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.open_temp_dir()', parent=
            playblast_form_layout)
        clear_temp_dir_btn = cmds.button(label='Clear Temp Folder', bgc=cls
            .BUTTON_COLOR_01, c='MayaToKeyframePro.clear_temp_dir()',
            parent=playblast_form_layout)
        version_label = cmds.text(label='v{0}'.format(cls.VERSION), align=
            'right')
        cmds.formLayout(playblast_form_layout, e=True, af=(cls.
            playblast_viewer_rbg, 'top', top_offset))
        cmds.formLayout(playblast_form_layout, e=True, ap=(cls.
            playblast_viewer_rbg, 'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(playblast_btn,
            'top', spacing, cls.playblast_viewer_rbg))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn,
            'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn,
            'right', 0, right_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(
            open_temp_dir_btn, 'top', spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            open_temp_dir_btn, 'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            open_temp_dir_btn, 'right', 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ac=(
            clear_temp_dir_btn, 'top', spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            clear_temp_dir_btn, 'left', 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            clear_temp_dir_btn, 'right', 0, right_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(version_label,
            'top', spacing, open_temp_dir_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(version_label,
            'right', 0, right_position))
        cls.update_sync_state()
        cls.on_collapse_changed()
        cmds.setFocus(cls.sync_btn)
        cmds.showWindow(cls.main_window)

    @classmethod
    def on_collapse_changed(cls):
        total_height = 0
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            total_height += cmds.frameLayout(layout, q=True, h=True)
        cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height)
        cls.update_collapse_state()

    @classmethod
    def update_sync_state(cls):
        if cls.sync_script_node_exists():
            cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR,
                label='SYNCED')
        else:
            cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01,
                label='SYNC')


<mask token>

<mask token>


class MayaToKeyframePro:
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    if sys.platform == 'win32':
        KEYFRAME_PRO_PATH = 'C:/Program Files/Keyframe Pro/bin/KeyframePro.exe'
    elif sys.platform == 'darwin':
        KEYFRAME_PRO_PATH = (
            '/Applications/KeyframePro.app/Contents/MacOS/KeyframePro')
    else:
        om.MGlobal.displayError(
            'MayaToKeyframePro is not supported on the current platform ({0})'
            .format(sys.platform))
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    @classmethod
    def open_keyframe_pro(cls, application_path=''):
        if not application_path:
            application_path = cls.KEYFRAME_PRO_PATH
        if not application_path:
            om.MGlobal.displayError('Keyframe Pro application path not set.')
        elif not os.path.exists(application_path):
            om.MGlobal.displayError(
                'Keyframe Pro application path does not exist: {0}'.format(
                application_path))
        else:
            try:
                subprocess.Popen(cls.KEYFRAME_PRO_PATH, shell=False, stdin=
                    None, stdout=None, stderr=None)
            except:
                traceback.print_exc()
                om.MGlobal.displayError(
                    'Failed to open Keyframe Pro. See script editor for details.'
                    )

    @classmethod
    def is_initialized(cls, display_errors=True):
        if not cls.kpro_client:
            cls.kpro_client = KeyframeProClient()
        if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors
            ):
            if cls.kpro_client.initialize():
                return True
        elif display_errors:
            om.MGlobal.displayError(
                'Connection failed. Application may be closed or the port may be in use ({0}).'
                .format(cls.PORT))
        if display_errors:
            om.MGlobal.displayError(
                'Failed to connect to Keyframe Pro. See script editor for details.'
                )
        return False

    @classmethod
    def toggle_sync(cls):
        if not cls.sync_script_node_exists() and cls.is_initialized():
            cls.create_sync_script_node()
            if cls.sync_script_node_exists():
                cls.update_sync_time()
        else:
            cls.delete_sync_script_node()
            cls.kpro_client.disconnect()
        cls.update_sync_state()

    @classmethod
    def update_sync_time(cls):
        frame = cmds.currentTime(q=True) + cls.get_sync_offset()
        from_range_start = bool(cls.get_from_range_start())
        if cls.kpro_client.set_frame(frame, False, from_range_start) <= 0:
            cls.toggle_sync()

    @classmethod
    def set_viewer_layout(cls, layout):
        if cls.is_initialized():
            cls.kpro_client.set_viewer_layout(layout)

    @classmethod
    def swap_timelines(cls):
        if cls.is_initialized():
            a = cls.kpro_client.get_active_in_viewer(0)
            b = cls.kpro_client.get_active_in_viewer(1)
            if b:
                cls.kpro_client.set_active_in_viewer(b['id'], 0)
            if a:
                cls.kpro_client.set_active_in_viewer(a['id'], 1)

    @classmethod
    def playblast(cls):
        format = cls.get_option_var('playblastFormat', 'avi')
        ext = ''
        if format == 'avi':
            ext = 'avi'
        elif format == 'qt' or format == 'avfoundation':
            ext = 'mov'
        elif format == 'image':
            ext = ''
        else:
            om.MGlobal.displayError('Unsupported playblast format: {0}'.
                format(format))
            return
        temp_dir = cls.get_temp_dir()
        if not temp_dir:
            om.MGlobal.displayError(
                'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                )
            return
        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)
        name = 'blast'
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        if format == 'image':
            file_path = '{0}/{1}_{2}'.format(temp_dir, name, timestamp)
        else:
            file_path = '{0}/{1}_{2}.{3}'.format(temp_dir, name, timestamp, ext
                )
        clear_cache = cls.get_option_var('playblastClearCache', True)
        show_ornaments = cls.get_option_var('playblastShowOrnaments', False)
        compression = cls.get_option_var('playblastCompression', 'none')
        quality = cls.get_option_var('playblastQuality', 70)
        percent = cls.get_option_var('playblastScale', 0.5) * 100
        padding = cls.get_option_var('playblastPadding', 4)
        display_source_size = cls.get_option_var('playblastDisplaySizeSource',
            1)
        playblast_width = cls.get_option_var('playblastWidth', 720)
        playblast_height = cls.get_option_var('playblastHeight', 480)
        args = {'format': format, 'clearCache': clear_cache, 'viewer': 
            False, 'showOrnaments': show_ornaments, 'fp': padding,
            'percent': percent, 'compression': compression, 'quality':
            quality, 'filename': file_path}
        if display_source_size == 2:
            args['widthHeight'] = [cmds.getAttr('defaultResolution.w'),
                cmds.getAttr('defaultResolution.h')]
        elif display_source_size == 3:
            args['widthHeight'] = [playblast_width, playblast_height]
        playback_slider = mel.eval('$tempVar = $gPlayBackSlider')
        if cmds.timeControl(playback_slider, q=True, rv=True):
            range = cmds.timeControl(playback_slider, q=True, ra=True)
            args['startTime'] = range[0]
            args['endTime'] = range[1]
        sound = cmds.timeControl(playback_slider, q=True, sound=True)
        if sound:
            args['sound'] = sound
        temp_path = cmds.playblast(**args)
        if temp_path:
            file_path = temp_path
        if not os.path.exists(file_path):
            om.MGlobal.displayError(
                'Playblast file does not exist. See script editor for details.'
                )
            return
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, query=
            True, select=True) - 1
        if viewer_index <= 1:
            if not cls.is_initialized(False):
                cls.open_keyframe_pro()
                time.sleep(cls.WAIT_FOR_OPEN_DURATION)
                if not cls.is_initialized():
                    om.MGlobal.displayError(
                        'Failed to open in viewer. See script editor for details.'
                        )
                    return
            if viewer_index >= 0 and viewer_index <= 1:
                source_in_a = None
                if viewer_index > 0:
                    source_in_a = cls.kpro_client.get_active_in_viewer(0)
                source = cls.kpro_client.import_file(file_path)
                if source:
                    cls.kpro_client.set_active_in_viewer(source['id'],
                        viewer_index)
                if source_in_a:
                    cls.kpro_client.set_active_in_viewer(source_in_a['id'], 0)
            autoplay = cls.kpro_client.is_autoplay()
            if autoplay:
                cls.kpro_client.set_playing(autoplay)

    @classmethod
    def get_option_var(cls, name, default):
        if cmds.optionVar(exists=name):
            return cmds.optionVar(q=name)
        else:
            return default
    <mask token>

    @classmethod
    def clear_temp_dir(cls):
        result = cmds.confirmDialog(title='Confirm', message=
            'Clear temporary directory?', button=['Yes', 'No'],
            defaultButton='Yes', cancelButton='No', dismissString='No')
        if result == 'Yes':
            temp_dir = cls.get_temp_dir()
            if temp_dir:
                errors_occurred = False
                for the_file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, the_file)
                    try:
                        if os.path.isfile(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except:
                        om.MGlobal.displayWarning('Failed to remove file: {0}'
                            .format(file_path))
                        om.MGlobal.displayWarning(
                            'File may be open in an application')
                        errors_occurred = True
                if errors_occurred:
                    om.MGlobal.displayWarning(
                        'Unable to remove all files. See script editor for details.'
                        )
                else:
                    om.MGlobal.displayInfo('Temporary directory cleared: {0}'
                        .format(temp_dir))
            else:
                om.MGlobal.displayError(
                    'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                    )

    @classmethod
    def get_temp_dir(cls):
        if cls.is_initialized(display_errors=False):
            config = cls.kpro_client.get_config()
            if config:
                cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config[
                    'temp_dir']])
                return config['temp_dir']
        temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, '')
        if not temp_dir:
            cls.open_keyframe_pro()
            time.sleep(cls.WAIT_FOR_OPEN_DURATION)
            if cls.is_initialized(display_errors=False):
                config = cls.kpro_client.get_config()
                if config:
                    cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR,
                        config['temp_dir']])
                    return config['temp_dir']
        return temp_dir
    <mask token>

    @classmethod
    def create_sync_script_node(cls):
        if not cls.sync_script_node_exists():
            cmds.scriptNode(scriptType=7, beforeScript=
                """try: MayaToKeyframePro.update_sync_time()
except: pass""",
                name=cls.SYNC_SCRIPT_NODE_NAME, sourceType='python')

    @classmethod
    def delete_sync_script_node(cls):
        if cls.sync_script_node_exists():
            cmds.delete(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def get_sync_offset(cls):
        if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR):
            return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR)
        else:
            return 0
    <mask token>

    @classmethod
    def sync_offset_to_current(cls):
        cls.set_sync_offset(-cmds.currentTime(q=True) + 1)

    @classmethod
    def sync_offset_changed(cls):
        cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True,
            value1=True))

    @classmethod
    def get_from_range_start(cls):
        if cmds.optionVar(exists=cls.FROM_RANGE_START_OPTION_VAR):
            return cmds.optionVar(q=cls.FROM_RANGE_START_OPTION_VAR)
        else:
            return 1

    @classmethod
    def update_from_range_start(cls):
        value = cmds.checkBox(cls.sync_from_range_start_cb, q=True, value=True)
        cmds.optionVar(iv=[cls.FROM_RANGE_START_OPTION_VAR, value])
        if cls.sync_script_node_exists():
            cls.update_sync_time()

    @classmethod
    def get_collapse_state(cls):
        if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR):
            collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR)
            if len(collapse_state) == 3:
                for value in collapse_state:
                    if value < 0 or value > 1:
                        return [0, 1, 1]
                return collapse_state
        return [0, 1, 1]

    @classmethod
    def update_collapse_state(cls):
        cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR)
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            collapse = cmds.frameLayout(layout, q=True, cl=True)
            cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse])

    @classmethod
    def display(cls):
        if KeyframeProClient.API_VERSION != MayaToKeyframePro.VERSION:
            om.MGlobal.displayInfo(
                'The Keyframe Pro Client API ({0}) and the MayaToKeyframePro script ({1}) versions do not match.'
                .format(KeyframeProClient.API_VERSION, MayaToKeyframePro.
                VERSION))
            om.MGlobal.displayInfo(
                'Please download and update to the latest versions: http://zurbrigg.com/keyframe-pro'
                )
            om.MGlobal.displayError(
                'Client API Version Mismatch (see the script editor for more details)'
                )
            return
        if cmds.window(cls.WINDOW_NAME, exists=True):
            cmds.deleteUI(cls.WINDOW_NAME, window=True)
        collapse_state = cls.get_collapse_state()
        cls.main_window = cmds.window(cls.WINDOW_NAME, title=cls.
            WINDOW_TITLE, s=True, tlb=False, rtf=True, mnb=False, mxb=False)
        main_layout = cmds.formLayout(parent=cls.main_window)
        cls.sync_layout = cmds.frameLayout(parent=main_layout, label='Sync',
            collapsable=True, cl=collapse_state[0], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        sync_form_layout = cmds.formLayout(parent=cls.sync_layout)
        cls.viewer_layout = cmds.frameLayout(parent=main_layout, label=
            'Viewer', collapsable=True, cl=collapse_state[1], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        viewer_form_layout = cmds.formLayout(parent=cls.viewer_layout)
        cls.playblast_layout = cmds.frameLayout(parent=main_layout, label=
            'Playblast', collapsable=True, cl=collapse_state[2], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        playblast_form_layout = cmds.formLayout(parent=cls.playblast_layout)
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'top', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'right', 0))
        cmds.formLayout(main_layout, e=True, ac=(cls.viewer_layout, 'top', 
            0, cls.sync_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, 'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, 'right', 0)
            )
        cmds.formLayout(main_layout, e=True, ac=(cls.playblast_layout,
            'top', 0, cls.viewer_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout,
            'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout,
            'right', 0))
        cls.sync_offset_ifg = cmds.intFieldGrp(label='Offset: ', value1=
            MayaToKeyframePro.get_sync_offset(), columnWidth2=(40, 48), cl2
            =('left', 'right'), cc=
            'MayaToKeyframePro.sync_offset_changed()', parent=sync_form_layout)
        cls.sync_from_range_start_cb = cmds.checkBox(label=
            'From Range Start', value=MayaToKeyframePro.
            get_from_range_start(), cc=
            'MayaToKeyframePro.update_from_range_start()', parent=
            sync_form_layout)
        sync_offset_to_current_btn = cmds.button(label='Current', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.sync_offset_to_current()',
            parent=sync_form_layout)
        reset_sync_offset_btn = cmds.button(label='  Reset  ', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.set_sync_offset(0)',
            parent=sync_form_layout)
        cls.sync_btn = cmds.button(label='SYNC', c=
            'MayaToKeyframePro.toggle_sync()', parent=sync_form_layout)
        top_offset = 1
        bottom_offset = 4
        left_position = 1
        right_position = 99
        spacing = 2
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_offset_ifg,
            'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_offset_ifg,
            'left', 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, af=(
            sync_offset_to_current_btn, 'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(
            sync_offset_to_current_btn, 'left', 0, cls.sync_offset_ifg))
        cmds.formLayout(sync_form_layout, e=True, af=(reset_sync_offset_btn,
            'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(reset_sync_offset_btn,
            'left', spacing, sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ac=(cls.
            sync_from_range_start_cb, 'top', top_offset,
            sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.
            sync_from_range_start_cb, 'left', 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_btn, 'top', 
            2 * spacing, cls.sync_from_range_start_cb))
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_btn,
            'bottom', bottom_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, 'left',
            0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, 'right',
            0, right_position))
        single_viewer_btn = cmds.button(label='Single', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('single')", parent=
            viewer_form_layout)
        hori_viewer_btn = cmds.button(label='Horizontal', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('horizontal')", parent=
            viewer_form_layout)
        vert_viewer_btn = cmds.button(label=' Vertical ', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('vertical')", parent=
            viewer_form_layout)
        swap_timelines_btn = cmds.button(label='Swap Timelines', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.swap_timelines()', parent
            =viewer_form_layout)
        cmds.formLayout(viewer_form_layout, e=True, af=(single_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn,
            'left', 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn,
            'right', 0, 38))
        cmds.formLayout(viewer_form_layout, e=True, af=(hori_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(hori_viewer_btn,
            'left', spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(hori_viewer_btn,
            'right', 0, 68))
        cmds.formLayout(viewer_form_layout, e=True, af=(vert_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(vert_viewer_btn,
            'left', spacing, hori_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(vert_viewer_btn,
            'right', 0, right_position))
        cmds.formLayout(viewer_form_layout, e=True, ac=(swap_timelines_btn,
            'top', spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, af=(swap_timelines_btn,
            'bottom', bottom_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn,
            'left', 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn,
            'right', 0, right_position))
        cls.playblast_viewer_rbg = cmds.radioButtonGrp(label=
            'Open in Viewer:   ', labelArray3=['A', 'B', 'None'],
            numberOfRadioButtons=3, select=1, cw4=(100, 40, 40, 40), cl4=(
            'left', 'left', 'left', 'left'), parent=playblast_form_layout)
        playblast_btn = cmds.button(label='PLAYBLAST', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.playblast()', parent=
            playblast_form_layout)
        open_temp_dir_btn = cmds.button(label='Open Temp Folder', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.open_temp_dir()', parent=
            playblast_form_layout)
        clear_temp_dir_btn = cmds.button(label='Clear Temp Folder', bgc=cls
            .BUTTON_COLOR_01, c='MayaToKeyframePro.clear_temp_dir()',
            parent=playblast_form_layout)
        version_label = cmds.text(label='v{0}'.format(cls.VERSION), align=
            'right')
        cmds.formLayout(playblast_form_layout, e=True, af=(cls.
            playblast_viewer_rbg, 'top', top_offset))
        cmds.formLayout(playblast_form_layout, e=True, ap=(cls.
            playblast_viewer_rbg, 'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(playblast_btn,
            'top', spacing, cls.playblast_viewer_rbg))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn,
            'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn,
            'right', 0, right_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(
            open_temp_dir_btn, 'top', spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            open_temp_dir_btn, 'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            open_temp_dir_btn, 'right', 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ac=(
            clear_temp_dir_btn, 'top', spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            clear_temp_dir_btn, 'left', 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            clear_temp_dir_btn, 'right', 0, right_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(version_label,
            'top', spacing, open_temp_dir_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(version_label,
            'right', 0, right_position))
        cls.update_sync_state()
        cls.on_collapse_changed()
        cmds.setFocus(cls.sync_btn)
        cmds.showWindow(cls.main_window)

    @classmethod
    def on_collapse_changed(cls):
        total_height = 0
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            total_height += cmds.frameLayout(layout, q=True, h=True)
        cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height)
        cls.update_collapse_state()

    @classmethod
    def update_sync_state(cls):
        if cls.sync_script_node_exists():
            cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR,
                label='SYNCED')
        else:
            cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01,
                label='SYNC')


<mask token>

<mask token>


class MayaToKeyframePro:
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    if sys.platform == 'win32':
        KEYFRAME_PRO_PATH = 'C:/Program Files/Keyframe Pro/bin/KeyframePro.exe'
    elif sys.platform == 'darwin':
        KEYFRAME_PRO_PATH = (
            '/Applications/KeyframePro.app/Contents/MacOS/KeyframePro')
    else:
        om.MGlobal.displayError(
            'MayaToKeyframePro is not supported on the current platform ({0})'
            .format(sys.platform))
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    @classmethod
    def open_keyframe_pro(cls, application_path=''):
        if not application_path:
            application_path = cls.KEYFRAME_PRO_PATH
        if not application_path:
            om.MGlobal.displayError('Keyframe Pro application path not set.')
        elif not os.path.exists(application_path):
            om.MGlobal.displayError(
                'Keyframe Pro application path does not exist: {0}'.format(
                application_path))
        else:
            try:
                subprocess.Popen(cls.KEYFRAME_PRO_PATH, shell=False, stdin=
                    None, stdout=None, stderr=None)
            except:
                traceback.print_exc()
                om.MGlobal.displayError(
                    'Failed to open Keyframe Pro. See script editor for details.'
                    )

    @classmethod
    def is_initialized(cls, display_errors=True):
        if not cls.kpro_client:
            cls.kpro_client = KeyframeProClient()
        if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors
            ):
            if cls.kpro_client.initialize():
                return True
        elif display_errors:
            om.MGlobal.displayError(
                'Connection failed. Application may be closed or the port may be in use ({0}).'
                .format(cls.PORT))
        if display_errors:
            om.MGlobal.displayError(
                'Failed to connect to Keyframe Pro. See script editor for details.'
                )
        return False

    @classmethod
    def toggle_sync(cls):
        if not cls.sync_script_node_exists() and cls.is_initialized():
            cls.create_sync_script_node()
            if cls.sync_script_node_exists():
                cls.update_sync_time()
        else:
            cls.delete_sync_script_node()
            cls.kpro_client.disconnect()
        cls.update_sync_state()

    @classmethod
    def update_sync_time(cls):
        frame = cmds.currentTime(q=True) + cls.get_sync_offset()
        from_range_start = bool(cls.get_from_range_start())
        if cls.kpro_client.set_frame(frame, False, from_range_start) <= 0:
            cls.toggle_sync()

    @classmethod
    def set_viewer_layout(cls, layout):
        if cls.is_initialized():
            cls.kpro_client.set_viewer_layout(layout)

    @classmethod
    def swap_timelines(cls):
        if cls.is_initialized():
            a = cls.kpro_client.get_active_in_viewer(0)
            b = cls.kpro_client.get_active_in_viewer(1)
            if b:
                cls.kpro_client.set_active_in_viewer(b['id'], 0)
            if a:
                cls.kpro_client.set_active_in_viewer(a['id'], 1)

    @classmethod
    def playblast(cls):
        format = cls.get_option_var('playblastFormat', 'avi')
        ext = ''
        if format == 'avi':
            ext = 'avi'
        elif format == 'qt' or format == 'avfoundation':
            ext = 'mov'
        elif format == 'image':
            ext = ''
        else:
            om.MGlobal.displayError('Unsupported playblast format: {0}'.
                format(format))
            return
        temp_dir = cls.get_temp_dir()
        if not temp_dir:
            om.MGlobal.displayError(
                'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                )
            return
        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)
        name = 'blast'
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        if format == 'image':
            file_path = '{0}/{1}_{2}'.format(temp_dir, name, timestamp)
        else:
            file_path = '{0}/{1}_{2}.{3}'.format(temp_dir, name, timestamp, ext
                )
        clear_cache = cls.get_option_var('playblastClearCache', True)
        show_ornaments = cls.get_option_var('playblastShowOrnaments', False)
        compression = cls.get_option_var('playblastCompression', 'none')
        quality = cls.get_option_var('playblastQuality', 70)
        percent = cls.get_option_var('playblastScale', 0.5) * 100
        padding = cls.get_option_var('playblastPadding', 4)
        display_source_size = cls.get_option_var('playblastDisplaySizeSource',
            1)
        playblast_width = cls.get_option_var('playblastWidth', 720)
        playblast_height = cls.get_option_var('playblastHeight', 480)
        args = {'format': format, 'clearCache': clear_cache, 'viewer': 
            False, 'showOrnaments': show_ornaments, 'fp': padding,
            'percent': percent, 'compression': compression, 'quality':
            quality, 'filename': file_path}
        if display_source_size == 2:
            args['widthHeight'] = [cmds.getAttr('defaultResolution.w'),
                cmds.getAttr('defaultResolution.h')]
        elif display_source_size == 3:
            args['widthHeight'] = [playblast_width, playblast_height]
        playback_slider = mel.eval('$tempVar = $gPlayBackSlider')
        if cmds.timeControl(playback_slider, q=True, rv=True):
            range = cmds.timeControl(playback_slider, q=True, ra=True)
            args['startTime'] = range[0]
            args['endTime'] = range[1]
        sound = cmds.timeControl(playback_slider, q=True, sound=True)
        if sound:
            args['sound'] = sound
        temp_path = cmds.playblast(**args)
        if temp_path:
            file_path = temp_path
        if not os.path.exists(file_path):
            om.MGlobal.displayError(
                'Playblast file does not exist. See script editor for details.'
                )
            return
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, query=
            True, select=True) - 1
        if viewer_index <= 1:
            if not cls.is_initialized(False):
                cls.open_keyframe_pro()
                time.sleep(cls.WAIT_FOR_OPEN_DURATION)
                if not cls.is_initialized():
                    om.MGlobal.displayError(
                        'Failed to open in viewer. See script editor for details.'
                        )
                    return
            if viewer_index >= 0 and viewer_index <= 1:
                source_in_a = None
                if viewer_index > 0:
                    source_in_a = cls.kpro_client.get_active_in_viewer(0)
                source = cls.kpro_client.import_file(file_path)
                if source:
                    cls.kpro_client.set_active_in_viewer(source['id'],
                        viewer_index)
                if source_in_a:
                    cls.kpro_client.set_active_in_viewer(source_in_a['id'], 0)
            autoplay = cls.kpro_client.is_autoplay()
            if autoplay:
                cls.kpro_client.set_playing(autoplay)

    @classmethod
    def get_option_var(cls, name, default):
        if cmds.optionVar(exists=name):
            return cmds.optionVar(q=name)
        else:
            return default
    <mask token>

    @classmethod
    def clear_temp_dir(cls):
        result = cmds.confirmDialog(title='Confirm', message=
            'Clear temporary directory?', button=['Yes', 'No'],
            defaultButton='Yes', cancelButton='No', dismissString='No')
        if result == 'Yes':
            temp_dir = cls.get_temp_dir()
            if temp_dir:
                errors_occurred = False
                for the_file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, the_file)
                    try:
                        if os.path.isfile(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except:
                        om.MGlobal.displayWarning('Failed to remove file: {0}'
                            .format(file_path))
                        om.MGlobal.displayWarning(
                            'File may be open in an application')
                        errors_occurred = True
                if errors_occurred:
                    om.MGlobal.displayWarning(
                        'Unable to remove all files. See script editor for details.'
                        )
                else:
                    om.MGlobal.displayInfo('Temporary directory cleared: {0}'
                        .format(temp_dir))
            else:
                om.MGlobal.displayError(
                    'Failed to get temp directory from Keyframe Pro. See script editor for details.'
                    )

    @classmethod
    def get_temp_dir(cls):
        if cls.is_initialized(display_errors=False):
            config = cls.kpro_client.get_config()
            if config:
                cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config[
                    'temp_dir']])
                return config['temp_dir']
        temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, '')
        if not temp_dir:
            cls.open_keyframe_pro()
            time.sleep(cls.WAIT_FOR_OPEN_DURATION)
            if cls.is_initialized(display_errors=False):
                config = cls.kpro_client.get_config()
                if config:
                    cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR,
                        config['temp_dir']])
                    return config['temp_dir']
        return temp_dir

    @classmethod
    def sync_script_node_exists(cls):
        return cmds.objExists(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def create_sync_script_node(cls):
        if not cls.sync_script_node_exists():
            cmds.scriptNode(scriptType=7, beforeScript=
                """try: MayaToKeyframePro.update_sync_time()
except: pass""",
                name=cls.SYNC_SCRIPT_NODE_NAME, sourceType='python')

    @classmethod
    def delete_sync_script_node(cls):
        if cls.sync_script_node_exists():
            cmds.delete(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def get_sync_offset(cls):
        if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR):
            return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR)
        else:
            return 0
    <mask token>

    @classmethod
    def sync_offset_to_current(cls):
        cls.set_sync_offset(-cmds.currentTime(q=True) + 1)

    @classmethod
    def sync_offset_changed(cls):
        cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True,
            value1=True))

    @classmethod
    def get_from_range_start(cls):
        if cmds.optionVar(exists=cls.FROM_RANGE_START_OPTION_VAR):
            return cmds.optionVar(q=cls.FROM_RANGE_START_OPTION_VAR)
        else:
            return 1

    @classmethod
    def update_from_range_start(cls):
        value = cmds.checkBox(cls.sync_from_range_start_cb, q=True, value=True)
        cmds.optionVar(iv=[cls.FROM_RANGE_START_OPTION_VAR, value])
        if cls.sync_script_node_exists():
            cls.update_sync_time()

    @classmethod
    def get_collapse_state(cls):
        if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR):
            collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR)
            if len(collapse_state) == 3:
                for value in collapse_state:
                    if value < 0 or value > 1:
                        return [0, 1, 1]
                return collapse_state
        return [0, 1, 1]

    @classmethod
    def update_collapse_state(cls):
        cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR)
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            collapse = cmds.frameLayout(layout, q=True, cl=True)
            cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse])

    @classmethod
    def display(cls):
        if KeyframeProClient.API_VERSION != MayaToKeyframePro.VERSION:
            om.MGlobal.displayInfo(
                'The Keyframe Pro Client API ({0}) and the MayaToKeyframePro script ({1}) versions do not match.'
                .format(KeyframeProClient.API_VERSION, MayaToKeyframePro.
                VERSION))
            om.MGlobal.displayInfo(
                'Please download and update to the latest versions: http://zurbrigg.com/keyframe-pro'
                )
            om.MGlobal.displayError(
                'Client API Version Mismatch (see the script editor for more details)'
                )
            return
        if cmds.window(cls.WINDOW_NAME, exists=True):
            cmds.deleteUI(cls.WINDOW_NAME, window=True)
        collapse_state = cls.get_collapse_state()
        cls.main_window = cmds.window(cls.WINDOW_NAME, title=cls.
            WINDOW_TITLE, s=True, tlb=False, rtf=True, mnb=False, mxb=False)
        main_layout = cmds.formLayout(parent=cls.main_window)
        cls.sync_layout = cmds.frameLayout(parent=main_layout, label='Sync',
            collapsable=True, cl=collapse_state[0], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        sync_form_layout = cmds.formLayout(parent=cls.sync_layout)
        cls.viewer_layout = cmds.frameLayout(parent=main_layout, label=
            'Viewer', collapsable=True, cl=collapse_state[1], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        viewer_form_layout = cmds.formLayout(parent=cls.viewer_layout)
        cls.playblast_layout = cmds.frameLayout(parent=main_layout, label=
            'Playblast', collapsable=True, cl=collapse_state[2], cc=
            'cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
            ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        playblast_form_layout = cmds.formLayout(parent=cls.playblast_layout)
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'top', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, 'right', 0))
        cmds.formLayout(main_layout, e=True, ac=(cls.viewer_layout, 'top', 
            0, cls.sync_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, 'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, 'right', 0)
            )
        cmds.formLayout(main_layout, e=True, ac=(cls.playblast_layout,
            'top', 0, cls.viewer_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout,
            'left', 0))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout,
            'right', 0))
        cls.sync_offset_ifg = cmds.intFieldGrp(label='Offset: ', value1=
            MayaToKeyframePro.get_sync_offset(), columnWidth2=(40, 48), cl2
            =('left', 'right'), cc=
            'MayaToKeyframePro.sync_offset_changed()', parent=sync_form_layout)
        cls.sync_from_range_start_cb = cmds.checkBox(label=
            'From Range Start', value=MayaToKeyframePro.
            get_from_range_start(), cc=
            'MayaToKeyframePro.update_from_range_start()', parent=
            sync_form_layout)
        sync_offset_to_current_btn = cmds.button(label='Current', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.sync_offset_to_current()',
            parent=sync_form_layout)
        reset_sync_offset_btn = cmds.button(label='  Reset  ', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.set_sync_offset(0)',
            parent=sync_form_layout)
        cls.sync_btn = cmds.button(label='SYNC', c=
            'MayaToKeyframePro.toggle_sync()', parent=sync_form_layout)
        top_offset = 1
        bottom_offset = 4
        left_position = 1
        right_position = 99
        spacing = 2
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_offset_ifg,
            'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_offset_ifg,
            'left', 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, af=(
            sync_offset_to_current_btn, 'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(
            sync_offset_to_current_btn, 'left', 0, cls.sync_offset_ifg))
        cmds.formLayout(sync_form_layout, e=True, af=(reset_sync_offset_btn,
            'top', top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(reset_sync_offset_btn,
            'left', spacing, sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ac=(cls.
            sync_from_range_start_cb, 'top', top_offset,
            sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.
            sync_from_range_start_cb, 'left', 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_btn, 'top', 
            2 * spacing, cls.sync_from_range_start_cb))
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_btn,
            'bottom', bottom_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, 'left',
            0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, 'right',
            0, right_position))
        single_viewer_btn = cmds.button(label='Single', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('single')", parent=
            viewer_form_layout)
        hori_viewer_btn = cmds.button(label='Horizontal', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('horizontal')", parent=
            viewer_form_layout)
        vert_viewer_btn = cmds.button(label=' Vertical ', bgc=cls.
            BUTTON_COLOR_01, c=
            "MayaToKeyframePro.set_viewer_layout('vertical')", parent=
            viewer_form_layout)
        swap_timelines_btn = cmds.button(label='Swap Timelines', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.swap_timelines()', parent
            =viewer_form_layout)
        cmds.formLayout(viewer_form_layout, e=True, af=(single_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn,
            'left', 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn,
            'right', 0, 38))
        cmds.formLayout(viewer_form_layout, e=True, af=(hori_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(hori_viewer_btn,
            'left', spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(hori_viewer_btn,
            'right', 0, 68))
        cmds.formLayout(viewer_form_layout, e=True, af=(vert_viewer_btn,
            'top', top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(vert_viewer_btn,
            'left', spacing, hori_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(vert_viewer_btn,
            'right', 0, right_position))
        cmds.formLayout(viewer_form_layout, e=True, ac=(swap_timelines_btn,
            'top', spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, af=(swap_timelines_btn,
            'bottom', bottom_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn,
            'left', 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn,
            'right', 0, right_position))
        cls.playblast_viewer_rbg = cmds.radioButtonGrp(label=
            'Open in Viewer:   ', labelArray3=['A', 'B', 'None'],
            numberOfRadioButtons=3, select=1, cw4=(100, 40, 40, 40), cl4=(
            'left', 'left', 'left', 'left'), parent=playblast_form_layout)
        playblast_btn = cmds.button(label='PLAYBLAST', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.playblast()', parent=
            playblast_form_layout)
        open_temp_dir_btn = cmds.button(label='Open Temp Folder', bgc=cls.
            BUTTON_COLOR_01, c='MayaToKeyframePro.open_temp_dir()', parent=
            playblast_form_layout)
        clear_temp_dir_btn = cmds.button(label='Clear Temp Folder', bgc=cls
            .BUTTON_COLOR_01, c='MayaToKeyframePro.clear_temp_dir()',
            parent=playblast_form_layout)
        version_label = cmds.text(label='v{0}'.format(cls.VERSION), align=
            'right')
        cmds.formLayout(playblast_form_layout, e=True, af=(cls.
            playblast_viewer_rbg, 'top', top_offset))
        cmds.formLayout(playblast_form_layout, e=True, ap=(cls.
            playblast_viewer_rbg, 'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(playblast_btn,
            'top', spacing, cls.playblast_viewer_rbg))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn,
            'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn,
            'right', 0, right_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(
            open_temp_dir_btn, 'top', spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            open_temp_dir_btn, 'left', 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            open_temp_dir_btn, 'right', 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ac=(
            clear_temp_dir_btn, 'top', spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            clear_temp_dir_btn, 'left', 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ap=(
            clear_temp_dir_btn, 'right', 0, right_position))
        cmds.formLayout(playblast_form_layout, e=True, ac=(version_label,
            'top', spacing, open_temp_dir_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(version_label,
            'right', 0, right_position))
        cls.update_sync_state()
        cls.on_collapse_changed()
        cmds.setFocus(cls.sync_btn)
        cmds.showWindow(cls.main_window)

    @classmethod
    def on_collapse_changed(cls):
        total_height = 0
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            total_height += cmds.frameLayout(layout, q=True, h=True)
        cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height)
        cls.update_collapse_state()

    @classmethod
    def update_sync_state(cls):
        if cls.sync_script_node_exists():
            cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR,
                label='SYNCED')
        else:
            cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01,
                label='SYNC')


<mask token>

import datetime
import os
import shutil
import subprocess
import sys
import time
import traceback

import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as om

from keyframe_pro.keyframe_pro_client import KeyframeProClient


class MayaToKeyframePro:

    WINDOW_NAME = "MayaToKeyframeProWindow"
    WINDOW_TITLE = "Keyframe Pro"

    VERSION = "1.4.1"

    KEYFRAME_PRO_PATH = ""
    if sys.platform == "win32":
        KEYFRAME_PRO_PATH = "C:/Program Files/Keyframe Pro/bin/KeyframePro.exe"
    elif sys.platform == "darwin":
        KEYFRAME_PRO_PATH = "/Applications/KeyframePro.app/Contents/MacOS/KeyframePro"
    else:
        om.MGlobal.displayError("MayaToKeyframePro is not supported on the current platform ({0})".format(sys.platform))

    PORT = 18181

    SYNC_SCRIPT_NODE_NAME = "MayaToKeyframeProScriptNode"

    CACHED_TEMP_DIR_OPTION_VAR = "MayaToKeyframeProCachedTempDir"
    COLLAPSE_STATE_OPTION_VAR = "MayaToKeyframeProCollapseState"
    SYNC_OFFSET_OPTION_VAR = "MayaToKeyframeProSyncOffset"
    FROM_RANGE_START_OPTION_VAR = "MayaToKeyframeProFromRangeStart"

    WAIT_FOR_OPEN_DURATION = 1  # Seconds to sleep after trying to open the application

    BUTTON_COLOR_01 = (0.5, 0.5, 0.5)
    BUTTON_COLOR_02 = (0.361, 0.361, 0.361)

    SYNC_ACTIVE_COLOR = (0.0, 0.5, 0.0)

    kpro_client = None

    main_window = None
    sync_layout = None
    viewer_layout = None
    playblast_layout = None

    sync_from_range_start_cb = None
    sync_offset_ifg = None
    playblast_viewer_rbg = None

    @classmethod
    def open_keyframe_pro(cls, application_path=""):
        if not application_path:
            application_path = cls.KEYFRAME_PRO_PATH

        if not application_path:
            om.MGlobal.displayError("Keyframe Pro application path not set.")
        elif not os.path.exists(application_path):
            om.MGlobal.displayError("Keyframe Pro application path does not exist: {0}".format(application_path))
        else:
            try:
                subprocess.Popen(cls.KEYFRAME_PRO_PATH, shell=False, stdin=None, stdout=None, stderr=None)
            except:
                traceback.print_exc()
                om.MGlobal.displayError("Failed to open Keyframe Pro. See script editor for details.")

    @classmethod
    def is_initialized(cls, display_errors=True):
        if not cls.kpro_client:
            cls.kpro_client = KeyframeProClient()

        if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors):
            if cls.kpro_client.initialize():
                return True
        else:
            if display_errors:
                om.MGlobal.displayError("Connection failed. Application may be closed or the port may be in use ({0}).".format(cls.PORT))

        if display_errors:
            om.MGlobal.displayError("Failed to connect to Keyframe Pro. See script editor for details.")

        return False

    @classmethod
    def toggle_sync(cls):
        if not cls.sync_script_node_exists() and cls.is_initialized():
            cls.create_sync_script_node()
            if cls.sync_script_node_exists():
                cls.update_sync_time()
        else:
            cls.delete_sync_script_node()
            cls.kpro_client.disconnect()

        cls.update_sync_state()

    @classmethod
    def update_sync_time(cls):
            frame = cmds.currentTime(q=True) + cls.get_sync_offset()
            from_range_start = bool(cls.get_from_range_start())
            if cls.kpro_client.set_frame(frame, False, from_range_start) <= 0:
                cls.toggle_sync()

    @classmethod
    def set_viewer_layout(cls, layout):
        if cls.is_initialized():
            cls.kpro_client.set_viewer_layout(layout)

    @classmethod
    def swap_timelines(cls):
        if cls.is_initialized():
            a = cls.kpro_client.get_active_in_viewer(0)
            b = cls.kpro_client.get_active_in_viewer(1)
            if b:
                cls.kpro_client.set_active_in_viewer(b["id"], 0)
            if a:
                cls.kpro_client.set_active_in_viewer(a["id"], 1)

    @classmethod
    def playblast(cls):

        format = cls.get_option_var("playblastFormat", "avi")
        ext = ""
        if format == "avi":
            ext = "avi"
        elif format == "qt" or format == "avfoundation":
            ext = "mov"
        elif format == "image":
            ext = ""
        else:
            om.MGlobal.displayError("Unsupported playblast format: {0}".format(format))
            return

        temp_dir = cls.get_temp_dir()
        if not temp_dir:
            om.MGlobal.displayError("Failed to get temp directory from Keyframe Pro. See script editor for details.")
            return

        if not os.path.exists(temp_dir):
            os.makedirs(temp_dir)

        name = "blast"
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        if format == "image":
            file_path = "{0}/{1}_{2}".format(temp_dir, name, timestamp)
        else:
            file_path = "{0}/{1}_{2}.{3}".format(temp_dir, name, timestamp, ext)

        clear_cache = cls.get_option_var("playblastClearCache", True)
        show_ornaments = cls.get_option_var("playblastShowOrnaments", False)
        compression = cls.get_option_var("playblastCompression", "none")
        quality = cls.get_option_var("playblastQuality", 70)
        percent = cls.get_option_var("playblastScale", 0.5) * 100
        padding = cls.get_option_var("playblastPadding", 4)
        display_source_size = cls.get_option_var("playblastDisplaySizeSource", 1)
        playblast_width = cls.get_option_var("playblastWidth", 720)
        playblast_height = cls.get_option_var("playblastHeight", 480)

        args = {"format": format,
                "clearCache": clear_cache,
                "viewer": False,
                "showOrnaments": show_ornaments,
                "fp": padding,
                "percent": percent,
                "compression": compression,
                "quality": quality,
                "filename": file_path
                }

        if display_source_size == 2:
            args["widthHeight"] = [cmds.getAttr("defaultResolution.w"), cmds.getAttr("defaultResolution.h")]
        elif display_source_size == 3:
            args["widthHeight"] = [playblast_width, playblast_height]

        playback_slider = mel.eval("$tempVar = $gPlayBackSlider")
        if(cmds.timeControl(playback_slider, q=True, rv=True)):
            range = cmds.timeControl(playback_slider, q=True, ra=True)
            args["startTime"] = range[0]
            args["endTime"] = range[1]

        sound = cmds.timeControl(playback_slider, q=True, sound=True)
        if sound:
            args["sound"] = sound

        temp_path = cmds.playblast(**args)
        if temp_path:
            file_path = temp_path
            
        if not os.path.exists(file_path):
            om.MGlobal.displayError("Playblast file does not exist. See script editor for details.")
            return
            
        # Open in viewer
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, query=True, select=True) - 1
        if viewer_index <= 1:
            if not cls.is_initialized(False):
                cls.open_keyframe_pro()
                time.sleep(cls.WAIT_FOR_OPEN_DURATION)

                if not cls.is_initialized():
                    om.MGlobal.displayError("Failed to open in viewer. See script editor for details.")
                    return

            if viewer_index >= 0 and viewer_index <= 1:
                # On import, source may be loaded into A. Restore current A if source is to be in B
                source_in_a = None
                if viewer_index > 0:
                    source_in_a = cls.kpro_client.get_active_in_viewer(0)

                # Swap
                source = cls.kpro_client.import_file(file_path)
                if source:
                    cls.kpro_client.set_active_in_viewer(source["id"], viewer_index)
                if source_in_a:
                    cls.kpro_client.set_active_in_viewer(source_in_a["id"], 0)

            autoplay = cls.kpro_client.is_autoplay()
            if(autoplay):
                cls.kpro_client.set_playing(autoplay)

    @classmethod
    def get_option_var(cls, name, default):
        if cmds.optionVar(exists=name):
            return cmds.optionVar(q=name)
        else:
            return default

    @classmethod
    def open_temp_dir(cls):
        temp_dir = cls.get_temp_dir()
        if temp_dir:
            if sys.platform == "win32":
                os.startfile(temp_dir, 'explore')
            elif sys.platform == "darwin":
                subprocess.Popen(["open", temp_dir])
            else:
                om.MGlobal.displayError("Open temp dir is not supported on the current platform ({0})".format(sys.platform))
        else:
            om.MGlobal.displayError("Failed to get temp directory from Keyframe Pro. See script editor for details.")

    @classmethod
    def clear_temp_dir(cls):
        result = cmds.confirmDialog(title='Confirm',
                                    message='Clear temporary directory?',
                                    button=['Yes', 'No'],
                                    defaultButton='Yes',
                                    cancelButton='No',
                                    dismissString='No')
        if result == "Yes":
            temp_dir = cls.get_temp_dir()
            if temp_dir:
                errors_occurred = False

                for the_file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, the_file)
                    try:
                        if os.path.isfile(file_path):
                            os.unlink(file_path)
                        elif os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                    except:
                        om.MGlobal.displayWarning("Failed to remove file: {0}".format(file_path))
                        om.MGlobal.displayWarning("File may be open in an application")
                        errors_occurred = True

                if errors_occurred:
                    om.MGlobal.displayWarning("Unable to remove all files. See script editor for details.")
                else:
                    om.MGlobal.displayInfo("Temporary directory cleared: {0}".format(temp_dir))
                    
            else:
                om.MGlobal.displayError("Failed to get temp directory from Keyframe Pro. See script editor for details.")

    @classmethod
    def get_temp_dir(cls):
        if cls.is_initialized(display_errors=False):
            config = cls.kpro_client.get_config()
            if config:
                cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config["temp_dir"]])
                return config["temp_dir"]

        temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, "")
        if not temp_dir:
            cls.open_keyframe_pro()
            time.sleep(cls.WAIT_FOR_OPEN_DURATION)
            
            if cls.is_initialized(display_errors=False):
                config = cls.kpro_client.get_config()
                if config:
                    cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config["temp_dir"]])
                    return config["temp_dir"]

        return temp_dir

    @classmethod
    def sync_script_node_exists(cls):
        return cmds.objExists(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def create_sync_script_node(cls):
        if not cls.sync_script_node_exists():
            cmds.scriptNode(scriptType=7,
                            beforeScript="try: MayaToKeyframePro.update_sync_time()\nexcept: pass",
                            name=cls.SYNC_SCRIPT_NODE_NAME,
                            sourceType="python")

    @classmethod
    def delete_sync_script_node(cls):
        if cls.sync_script_node_exists():
            cmds.delete(cls.SYNC_SCRIPT_NODE_NAME)

    @classmethod
    def get_sync_offset(cls):
        if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR):
            return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR)
        else:
            return 0

    @classmethod
    def set_sync_offset(cls, value):
        cmds.intFieldGrp(cls.sync_offset_ifg, e=True, value1=value)
        cmds.optionVar(iv=[cls.SYNC_OFFSET_OPTION_VAR, value])
        if (cls.sync_script_node_exists()):
            cls.update_sync_time()

    @classmethod
    def sync_offset_to_current(cls):
        cls.set_sync_offset(-cmds.currentTime(q=True) + 1)

    @classmethod
    def sync_offset_changed(cls):
        cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True, value1=True))

    @classmethod
    def get_from_range_start(cls):
        if cmds.optionVar(exists=cls.FROM_RANGE_START_OPTION_VAR):
            return cmds.optionVar(q=cls.FROM_RANGE_START_OPTION_VAR)
        else:
            return 1

    @classmethod
    def update_from_range_start(cls):
        value = cmds.checkBox(cls.sync_from_range_start_cb, q=True, value=True)
        cmds.optionVar(iv=[cls.FROM_RANGE_START_OPTION_VAR, value])

        if cls.sync_script_node_exists():
            cls.update_sync_time()

    @classmethod
    def get_collapse_state(cls):
        if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR):
            collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR)
            if len(collapse_state) == 3:
                for value in collapse_state:
                    if value < 0 or value > 1:
                        return [0, 1, 1]

                return collapse_state

        return [0, 1, 1]

    @classmethod
    def update_collapse_state(cls):
        cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR)
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            collapse = cmds.frameLayout(layout, q=True, cl=True)
            cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse])

    @classmethod
    def display(cls):

        if(KeyframeProClient.API_VERSION != MayaToKeyframePro.VERSION):
            om.MGlobal.displayInfo("The Keyframe Pro Client API ({0}) and the MayaToKeyframePro script ({1}) versions do not match.".format(KeyframeProClient.API_VERSION, MayaToKeyframePro.VERSION))
            om.MGlobal.displayInfo("Please download and update to the latest versions: http://zurbrigg.com/keyframe-pro")
            om.MGlobal.displayError("Client API Version Mismatch (see the script editor for more details)")
            return

        if cmds.window(cls.WINDOW_NAME, exists=True):
            cmds.deleteUI(cls.WINDOW_NAME, window=True)

        collapse_state = cls.get_collapse_state()

        # ---------------------------------------------------------------------
        # Main layout
        # ---------------------------------------------------------------------
        cls.main_window = cmds.window(cls.WINDOW_NAME, title=cls.WINDOW_TITLE, s=True, tlb=False, rtf=True, mnb=False, mxb=False)
        main_layout = cmds.formLayout(parent=cls.main_window)

        cls.sync_layout = cmds.frameLayout(parent=main_layout,
                                           label="Sync", collapsable=True,
                                           cl=collapse_state[0],
                                           cc='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
                                           ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        sync_form_layout = cmds.formLayout(parent=cls.sync_layout)

        cls.viewer_layout = cmds.frameLayout(parent=main_layout,
                                             label="Viewer",
                                             collapsable=True,
                                             cl=collapse_state[1],
                                             cc='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
                                             ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        viewer_form_layout = cmds.formLayout(parent=cls.viewer_layout)

        cls.playblast_layout = cmds.frameLayout(parent=main_layout,
                                                label="Playblast",
                                                collapsable=True,
                                                cl=collapse_state[2],
                                                cc='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")',
                                                ec='cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")')
        playblast_form_layout = cmds.formLayout(parent=cls.playblast_layout)

        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "top", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "left", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "right", 0))

        cmds.formLayout(main_layout, e=True, ac=(cls.viewer_layout, "top", 0, cls.sync_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, "left", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, "right", 0))

        cmds.formLayout(main_layout, e=True, ac=(cls.playblast_layout, "top", 0, cls.viewer_layout))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout, "left", 0))
        cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout, "right", 0))

        # ---------------------------------------------------------------------
        # Sync layout
        # ---------------------------------------------------------------------
        cls.sync_offset_ifg = cmds.intFieldGrp(label="Offset: ",
                                               value1=MayaToKeyframePro.get_sync_offset(),
                                               columnWidth2=(40, 48),
                                               cl2=("left", "right"),
                                               cc="MayaToKeyframePro.sync_offset_changed()",
                                               parent=sync_form_layout)

        cls.sync_from_range_start_cb = cmds.checkBox(label="From Range Start",
                                                     value=MayaToKeyframePro.get_from_range_start(),
                                                     cc="MayaToKeyframePro.update_from_range_start()",
                                                     parent=sync_form_layout)

        sync_offset_to_current_btn = cmds.button(label="Current",
                                                 bgc=cls.BUTTON_COLOR_01,
                                                 c="MayaToKeyframePro.sync_offset_to_current()",
                                                 parent=sync_form_layout)

        reset_sync_offset_btn = cmds.button(label="  Reset  ",
                                            bgc=cls.BUTTON_COLOR_01,
                                            c="MayaToKeyframePro.set_sync_offset(0)",
                                            parent=sync_form_layout)

        cls.sync_btn = cmds.button(label="SYNC", c="MayaToKeyframePro.toggle_sync()", parent=sync_form_layout)

        top_offset = 1
        bottom_offset = 4
        left_position = 1
        right_position = 99
        spacing = 2
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_offset_ifg, "top", top_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_offset_ifg, "left", 0, left_position))

        cmds.formLayout(sync_form_layout, e=True, af=(sync_offset_to_current_btn, "top", top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(sync_offset_to_current_btn, "left", 0, cls.sync_offset_ifg))

        cmds.formLayout(sync_form_layout, e=True, af=(reset_sync_offset_btn, "top", top_offset))
        cmds.formLayout(sync_form_layout, e=True, ac=(reset_sync_offset_btn, "left", spacing, sync_offset_to_current_btn))

        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_from_range_start_cb, "top", top_offset, sync_offset_to_current_btn))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_from_range_start_cb, "left", 0, left_position))

        cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_btn, "top", 2 * spacing, cls.sync_from_range_start_cb))
        cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_btn, "bottom", bottom_offset))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, "left", 0, left_position))
        cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, "right", 0, right_position))

        # ---------------------------------------------------------------------
        # Viewer layout
        # ---------------------------------------------------------------------
        single_viewer_btn = cmds.button(label="Single",
                                        bgc=cls.BUTTON_COLOR_01,
                                        c="MayaToKeyframePro.set_viewer_layout('single')",
                                        parent=viewer_form_layout)

        hori_viewer_btn = cmds.button(label="Horizontal",
                                      bgc=cls.BUTTON_COLOR_01,
                                      c="MayaToKeyframePro.set_viewer_layout('horizontal')",
                                      parent=viewer_form_layout)

        vert_viewer_btn = cmds.button(label=" Vertical ",
                                      bgc=cls.BUTTON_COLOR_01,
                                      c="MayaToKeyframePro.set_viewer_layout('vertical')",
                                      parent=viewer_form_layout)

        swap_timelines_btn = cmds.button(label="Swap Timelines",
                                         bgc=cls.BUTTON_COLOR_01,
                                         c="MayaToKeyframePro.swap_timelines()",
                                         parent=viewer_form_layout)

        cmds.formLayout(viewer_form_layout, e=True, af=(single_viewer_btn, "top", top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn, "left", 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn, "right", 0, 38))

        cmds.formLayout(viewer_form_layout, e=True, af=(hori_viewer_btn, "top", top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(hori_viewer_btn, "left", spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(hori_viewer_btn, "right", 0, 68))

        cmds.formLayout(viewer_form_layout, e=True, af=(vert_viewer_btn, "top", top_offset))
        cmds.formLayout(viewer_form_layout, e=True, ac=(vert_viewer_btn, "left", spacing, hori_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, ap=(vert_viewer_btn, "right", 0, right_position))

        cmds.formLayout(viewer_form_layout, e=True, ac=(swap_timelines_btn, "top", spacing, single_viewer_btn))
        cmds.formLayout(viewer_form_layout, e=True, af=(swap_timelines_btn, "bottom", bottom_offset))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn, "left", 0, left_position))
        cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn, "right", 0, right_position))

        # ---------------------------------------------------------------------
        # Playblast layout
        # ---------------------------------------------------------------------
        cls.playblast_viewer_rbg = cmds.radioButtonGrp(label='Open in Viewer:   ',
                                                       labelArray3=['A', 'B', 'None'],
                                                       numberOfRadioButtons=3,
                                                       select=1,
                                                       cw4=(100, 40, 40, 40),
                                                       cl4=("left", "left", "left", "left"),
                                                       parent=playblast_form_layout)

        playblast_btn = cmds.button(label="PLAYBLAST",
                                    bgc=cls.BUTTON_COLOR_01,
                                    c="MayaToKeyframePro.playblast()",
                                    parent=playblast_form_layout)

        open_temp_dir_btn = cmds.button(label="Open Temp Folder",
                                        bgc=cls.BUTTON_COLOR_01,
                                        c="MayaToKeyframePro.open_temp_dir()",
                                        parent=playblast_form_layout)

        clear_temp_dir_btn = cmds.button(label="Clear Temp Folder",
                                         bgc=cls.BUTTON_COLOR_01,
                                         c="MayaToKeyframePro.clear_temp_dir()",
                                         parent=playblast_form_layout)

        version_label = cmds.text(label="v{0}".format(cls.VERSION), align="right")

        cmds.formLayout(playblast_form_layout, e=True, af=(cls.playblast_viewer_rbg, "top", top_offset))
        cmds.formLayout(playblast_form_layout, e=True, ap=(cls.playblast_viewer_rbg, "left", 0, left_position))

        cmds.formLayout(playblast_form_layout, e=True, ac=(playblast_btn, "top", spacing, cls.playblast_viewer_rbg))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn, "left", 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn, "right", 0, right_position))

        cmds.formLayout(playblast_form_layout, e=True, ac=(open_temp_dir_btn, "top", spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(open_temp_dir_btn, "left", 0, left_position))
        cmds.formLayout(playblast_form_layout, e=True, ap=(open_temp_dir_btn, "right", 1, 50))

        cmds.formLayout(playblast_form_layout, e=True, ac=(clear_temp_dir_btn, "top", spacing, playblast_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(clear_temp_dir_btn, "left", 1, 50))
        cmds.formLayout(playblast_form_layout, e=True, ap=(clear_temp_dir_btn, "right", 0, right_position))

        cmds.formLayout(playblast_form_layout, e=True, ac=(version_label, "top", spacing, open_temp_dir_btn))
        cmds.formLayout(playblast_form_layout, e=True, ap=(version_label, "right", 0, right_position))

        # ---------------------------------------------------------------------
        # Update and show
        # ---------------------------------------------------------------------
        cls.update_sync_state()
        cls.on_collapse_changed()
        cmds.setFocus(cls.sync_btn)

        cmds.showWindow(cls.main_window)

    @classmethod
    def on_collapse_changed(cls):
        total_height = 0
        layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout]
        for layout in layouts:
            total_height += cmds.frameLayout(layout, q=True, h=True)

        cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height)
        cls.update_collapse_state()

    @classmethod
    def update_sync_state(cls):
        if cls.sync_script_node_exists():
            cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR, label="SYNCED")
        else:
            cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01, label="SYNC")


if __name__ == "__main__":
    MayaToKeyframePro.display()
