Updated
This commit is contained in:
		
							
								
								
									
										6
									
								
								Scripts/Animation/keyframe_pro/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Scripts/Animation/keyframe_pro/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from . import keyframe_pro
 | 
			
		||||
from . import keyframe_pro_maya
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								Scripts/Animation/keyframe_pro/keyframe_pro/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Scripts/Animation/keyframe_pro/keyframe_pro/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from . import *
 | 
			
		||||
@@ -0,0 +1,684 @@
 | 
			
		||||
import json
 | 
			
		||||
import socket
 | 
			
		||||
import time
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
class KeyframeProClient(object):
 | 
			
		||||
    """
 | 
			
		||||
    Client API for Keyframe Pro
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    API_VERSION = "1.0.1"
 | 
			
		||||
 | 
			
		||||
    PORT = 18181
 | 
			
		||||
 | 
			
		||||
    HEADER_SIZE = 10
 | 
			
		||||
 | 
			
		||||
    kpro_socket = None
 | 
			
		||||
    kpro_initialized = False
 | 
			
		||||
 | 
			
		||||
    def __init__(self, timeout=2):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
        self.show_timeout_errors = True
 | 
			
		||||
 | 
			
		||||
    def connect(self, port=-1, display_errors=True):
 | 
			
		||||
        """
 | 
			
		||||
        Create a connection with the application.
 | 
			
		||||
 | 
			
		||||
        :param port: The port Keyframe Pro is listening on.
 | 
			
		||||
        :return: True if connection was successful (or already exists). False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        if self.is_connected():
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        if port < 0:
 | 
			
		||||
            port = self.__class__.PORT
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.__class__.kpro_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
			
		||||
            self.__class__.kpro_socket.connect(("localhost", port))
 | 
			
		||||
            self.__class__.kpro_socket.setblocking(0)
 | 
			
		||||
 | 
			
		||||
            self.__class__.kpro_initialized = False
 | 
			
		||||
 | 
			
		||||
        except:
 | 
			
		||||
            self.__class__.kpro_socket = None
 | 
			
		||||
            if display_errors:
 | 
			
		||||
                traceback.print_exc()
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def disconnect(self):
 | 
			
		||||
        """
 | 
			
		||||
        Disconnect from the application.
 | 
			
		||||
 | 
			
		||||
        :return: True if the existing connection was disconnect successfully. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        result = False
 | 
			
		||||
        if self.__class__.kpro_socket:
 | 
			
		||||
            try:
 | 
			
		||||
                self.__class__.kpro_socket.close()
 | 
			
		||||
                result = True
 | 
			
		||||
            except:
 | 
			
		||||
                traceback.print_exc()
 | 
			
		||||
 | 
			
		||||
        self.__class__.kpro_socket = None
 | 
			
		||||
        self.__class__.kpro_initialized = False
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def is_connected(self):
 | 
			
		||||
        """
 | 
			
		||||
        Test if a connection exists.
 | 
			
		||||
 | 
			
		||||
        :return: True if a connection exists. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        self.show_timeout_errors = False
 | 
			
		||||
        connected = self.__class__.kpro_socket and self.echo("conn")
 | 
			
		||||
        self.show_timeout_errors = True
 | 
			
		||||
 | 
			
		||||
        if connected:
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def send(self, cmd):
 | 
			
		||||
        """
 | 
			
		||||
        Send a command to the application and wait for a processed reply.
 | 
			
		||||
 | 
			
		||||
        :param cmd: Dictionary containing the cmd and args
 | 
			
		||||
        :return: Variable depending on cmd.
 | 
			
		||||
        """
 | 
			
		||||
        json_cmd = json.dumps(cmd)
 | 
			
		||||
 | 
			
		||||
        message = []
 | 
			
		||||
        message.append("{:10d}".format(len(json_cmd)))  # header
 | 
			
		||||
        message.append(json_cmd)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.__class__.kpro_socket.sendall("".join(message))
 | 
			
		||||
        except:
 | 
			
		||||
            traceback.print_exc()
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        return self.recv(cmd)
 | 
			
		||||
 | 
			
		||||
    def recv(self, cmd):
 | 
			
		||||
        """
 | 
			
		||||
        Wait for the application to reply to a previously sent cmd.
 | 
			
		||||
 | 
			
		||||
        :param cmd: Dictionary containing the cmd and args
 | 
			
		||||
        :return: Variable depending on cmd.
 | 
			
		||||
        """
 | 
			
		||||
        total_data = []
 | 
			
		||||
        data = ""
 | 
			
		||||
        reply_length = 0
 | 
			
		||||
        bytes_remaining = self.__class__.HEADER_SIZE
 | 
			
		||||
 | 
			
		||||
        begin = time.time()
 | 
			
		||||
        while time.time() - begin < self.timeout:
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                data = self.__class__.kpro_socket.recv(bytes_remaining)
 | 
			
		||||
            except:
 | 
			
		||||
                time.sleep(0.01)
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if data:
 | 
			
		||||
                total_data.append(data)
 | 
			
		||||
 | 
			
		||||
                bytes_remaining -= len(data)
 | 
			
		||||
                if(bytes_remaining <= 0):
 | 
			
		||||
 | 
			
		||||
                    if reply_length == 0:
 | 
			
		||||
                        header = "".join(total_data)
 | 
			
		||||
                        reply_length = int(header)
 | 
			
		||||
                        bytes_remaining = reply_length
 | 
			
		||||
                        total_data = []
 | 
			
		||||
                    else:
 | 
			
		||||
                        reply_json = "".join(total_data)
 | 
			
		||||
                        return json.loads(reply_json)
 | 
			
		||||
 | 
			
		||||
        if self.show_timeout_errors:
 | 
			
		||||
            if "cmd" in cmd.keys():
 | 
			
		||||
                cmd_name = cmd["cmd"]
 | 
			
		||||
                print('[KPRO][ERROR] "{0}" timed out.'.format(cmd_name))
 | 
			
		||||
            else:
 | 
			
		||||
                print('[KPRO][ERROR] Unknown cmd timed out.')
 | 
			
		||||
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def is_valid_reply(self, reply):
 | 
			
		||||
        """
 | 
			
		||||
        Test if a reply from the application is valid. Output any messages.
 | 
			
		||||
 | 
			
		||||
        :param reply: Dictionary containing the response to a cmd
 | 
			
		||||
        :return: True if valid. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        if not reply:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not reply["success"]:
 | 
			
		||||
            print('[KPRO][ERROR] "{0}" failed: {1}'.format(reply["cmd"], reply["msg"]))
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def initialize(self):
 | 
			
		||||
        """
 | 
			
		||||
        One time initialization required by the application.
 | 
			
		||||
 | 
			
		||||
        :return: True if successfully initalized. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        if self.__class__.kpro_initialized:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "initialize",
 | 
			
		||||
            "api_version": self.__class__.API_VERSION
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if reply and reply["success"] and reply["result"] == 0:
 | 
			
		||||
            self.__class__.kpro_initialized = True
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            print('[KPRO][ERROR] Initialization failed: {0}'.format(reply["msg"]))
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    # COMMANDS
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    def echo(self, text):
 | 
			
		||||
        """
 | 
			
		||||
        Get an echo response from the application.
 | 
			
		||||
 | 
			
		||||
        :param text: The string to be sent to the application.
 | 
			
		||||
        :return: A string containing the text on success. None otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "echo",
 | 
			
		||||
            "text": text
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["result"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_config(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the configuration settings for the application.
 | 
			
		||||
 | 
			
		||||
        :return: Dictionary containing the config values.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_config"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def new_project(self, empty=False):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new project.
 | 
			
		||||
 | 
			
		||||
        :param empty: Create an empty project.
 | 
			
		||||
        :return: True if new project created. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "new_project",
 | 
			
		||||
            "empty": empty
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def open_project(self, file_path):
 | 
			
		||||
        """
 | 
			
		||||
        Open an existing project.
 | 
			
		||||
 | 
			
		||||
        :param file_path: Path to the project.
 | 
			
		||||
        :return: True if the project is opened. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "open_project",
 | 
			
		||||
            "file_path": file_path
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def save_project(self, file_path):
 | 
			
		||||
        """
 | 
			
		||||
        Save the current project.
 | 
			
		||||
 | 
			
		||||
        :param file_path: Path to the project.
 | 
			
		||||
        :return: True if the project is saved. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "save_project",
 | 
			
		||||
            "file_path": file_path
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def get_project_path(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the path to the current project.
 | 
			
		||||
 | 
			
		||||
        :return: The path to the project. None if there is an error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_project_path"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["project_path"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def import_file(self, file_path, name="", range_start=-1, range_end=-1, parent_id=""):
 | 
			
		||||
        """
 | 
			
		||||
        Import a source file into the project.
 | 
			
		||||
 | 
			
		||||
        :param file_path: Path to the source
 | 
			
		||||
        :param name: Name of the source
 | 
			
		||||
        :param range_start: Range start frame
 | 
			
		||||
        :param range_end: Range end frame
 | 
			
		||||
        :param parent_id: Parent folder of the source
 | 
			
		||||
        :return: Dictionary representing the source object. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "import_file",
 | 
			
		||||
            "file_path": file_path,
 | 
			
		||||
            "name": name,
 | 
			
		||||
            "range_start": range_start,
 | 
			
		||||
            "range_end": range_end,
 | 
			
		||||
            "parent_id": parent_id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["source"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def add_folder(self, name="", parent_id=""):
 | 
			
		||||
        """
 | 
			
		||||
        Add a folder to the project.
 | 
			
		||||
 | 
			
		||||
        :param name: Name of the folder
 | 
			
		||||
        :param parent_id: Parent folder of the folder
 | 
			
		||||
        :return: Dictionary representing the folder object. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "add_folder",
 | 
			
		||||
            "name": name,
 | 
			
		||||
            "parent_id": parent_id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["folder"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def add_timeline(self, name="", parent_id=""):
 | 
			
		||||
        """
 | 
			
		||||
        Add a timeline to the project.
 | 
			
		||||
 | 
			
		||||
        :param name: Name of the timeline
 | 
			
		||||
        :param parent_id: Parent folder of the timeline
 | 
			
		||||
        :return: Dictionary representing the timeline object. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "add_timeline",
 | 
			
		||||
            "name": name,
 | 
			
		||||
            "parent_id": parent_id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["timeline"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def remove(self, id, force=False):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a folder, timeline or source from the project.
 | 
			
		||||
 | 
			
		||||
        :param id: ID for the object to be removed.
 | 
			
		||||
        :param force: (Source only) Force removal if the source is in use in one or more timelines.
 | 
			
		||||
        :return: True on successful removal. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "remove",
 | 
			
		||||
            "id": id,
 | 
			
		||||
            "force": force
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def update(self, obj):
 | 
			
		||||
        """
 | 
			
		||||
        Update the folder, timeline or source object with the values contained in the obj dict.
 | 
			
		||||
        Editable obj values that are different will be modified.
 | 
			
		||||
 | 
			
		||||
        :param obj: Dictionary representing the object to be updated.
 | 
			
		||||
        :return: Dictionary representing the updated object. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "update",
 | 
			
		||||
            "object": obj
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["updated"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def insert_element_in_timeline(self, source_id, timeline_id, index=-1):
 | 
			
		||||
        """
 | 
			
		||||
        Insert a source element into a timeline.
 | 
			
		||||
 | 
			
		||||
        :param source_id: ID of the source to be added to the timeline.
 | 
			
		||||
        :param timeline_id: ID of the timeline.
 | 
			
		||||
        :param index: Index to insert source at. Inserted at the end if index is out of range.
 | 
			
		||||
        :return: True on successful insertion. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "insert_element_in_timeline",
 | 
			
		||||
            "source_id": source_id,
 | 
			
		||||
            "timeline_id": timeline_id,
 | 
			
		||||
            "index": index
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def remove_element_from_timeline(self, timeline_id, index):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a source element from a timeline.
 | 
			
		||||
 | 
			
		||||
        :param timeline_id: ID of the timeline.
 | 
			
		||||
        :param index: Index of the element to be removed.
 | 
			
		||||
        :return: True on successful removal. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "remove_element_from_timeline",
 | 
			
		||||
            "timeline_id": timeline_id,
 | 
			
		||||
            "index": index
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def get_timeline_elements(self, timeline_id):
 | 
			
		||||
        """
 | 
			
		||||
        Get an ordered list of the sources in a timeline.
 | 
			
		||||
 | 
			
		||||
        :param timeline_id: ID of the timeline.
 | 
			
		||||
        :return: An ordered list of dictionaries representing the sources in a timeline. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_timeline_elements",
 | 
			
		||||
            "timeline_id": timeline_id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["sources"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_folders(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get an unordered list of the folders in the project.
 | 
			
		||||
 | 
			
		||||
        :return: An unordered list of dictionaries representing the folders in the project. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_folders"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["folders"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_timelines(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get an unordered list of timelines in the project.
 | 
			
		||||
 | 
			
		||||
        :return: An unordered list of dictionaries representing the timelines in the project. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_timelines"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["timelines"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_sources(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get an unordered list of sources in the project.
 | 
			
		||||
 | 
			
		||||
        :return: An unordered list of dictionaries representing the sources in the project. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_sources"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["sources"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_frame(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the current frame of the (primary) active timeline.
 | 
			
		||||
 | 
			
		||||
        :return: The frame of the (primary) active timeline. Zero on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_frame"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["frame"]
 | 
			
		||||
        else:
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def set_frame(self, frame, audio=False):
 | 
			
		||||
        """
 | 
			
		||||
        Set the current frame of the (primary) active timeline.
 | 
			
		||||
 | 
			
		||||
        :param frame: Requested frame number
 | 
			
		||||
        :param audio: Play audio for the frame after setting it.
 | 
			
		||||
        :return: The frame of the (primary) active timeline. Zero on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "set_frame",
 | 
			
		||||
            "frame": frame,
 | 
			
		||||
            "audio": audio
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["frame"]
 | 
			
		||||
        else:
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def get_range(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the current range of the (primary) active timeline.
 | 
			
		||||
 | 
			
		||||
        :return: Tuple containing the range of the (primary) active timeline. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_range"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return (reply["start_frame"], reply["end_frame"])
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def set_range(self, start_frame, end_frame):
 | 
			
		||||
        """
 | 
			
		||||
        Set the current range of the (primary) active timeline.
 | 
			
		||||
 | 
			
		||||
        :param start_frame: Requested range start frame number.
 | 
			
		||||
        :param end_frame: Requested range end frame number.
 | 
			
		||||
        :return: Tuple containing the range of the (primary) active timeline. None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "set_range",
 | 
			
		||||
            "start_frame": start_frame,
 | 
			
		||||
            "end_frame": end_frame
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return (reply["start_frame"], reply["end_frame"])
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_default_timeline(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the project default timeline.
 | 
			
		||||
 | 
			
		||||
        Imported files (sources) are automatically added to this timeline.
 | 
			
		||||
 | 
			
		||||
        :return: Dictionary representing the timeline object (may be empty if unassigned). None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_default_timeline"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["timeline"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def set_default_timeline(self, id):
 | 
			
		||||
        """
 | 
			
		||||
        Set the project default timeline. An empty 'id' string will result unassign the default timeline.
 | 
			
		||||
 | 
			
		||||
        Imported files (sources) are automatically added to this timeline.
 | 
			
		||||
 | 
			
		||||
        :return: Dictionary representing the timeline object (may be empty if unassigned). None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "set_default_timeline",
 | 
			
		||||
            "id": id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def get_active_in_viewer(self, index):
 | 
			
		||||
        """
 | 
			
		||||
        Get the source/timeline assigned to a viewer.
 | 
			
		||||
 | 
			
		||||
        :param index: Viewer index. (0 - Viewer A, 1 - Viewer B)
 | 
			
		||||
        :return: Dictionary representing the timeline object (may be empty if unassigned). None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "get_active_in_viewer",
 | 
			
		||||
            "index": index
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return reply["timeline"]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def set_active_in_viewer(self, id, index):
 | 
			
		||||
        """
 | 
			
		||||
        Set the source/timeline assigned to a viewer.
 | 
			
		||||
 | 
			
		||||
        An empty 'id' string will unassign a timeline from the viewer.
 | 
			
		||||
 | 
			
		||||
        :param index: Viewer index. (0 - Viewer A, 1 - Viewer B)
 | 
			
		||||
        :return: Dictionary representing the timeline object (may be empty if unassigned). None on error.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "set_active_in_viewer",
 | 
			
		||||
            "id": id,
 | 
			
		||||
            "index": index
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def set_viewer_layout(self, layout="single"):
 | 
			
		||||
        """
 | 
			
		||||
        Set the viewer layout to single, split horizontal or split vertical.
 | 
			
		||||
 | 
			
		||||
        :param layout: Desired layout ("single", "horizontal" or "vertical")
 | 
			
		||||
        :return: True on success. False otherwise.
 | 
			
		||||
        """
 | 
			
		||||
        cmd = {
 | 
			
		||||
            "cmd": "set_viewer_layout",
 | 
			
		||||
            "layout": layout,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        reply = self.send(cmd)
 | 
			
		||||
        if self.is_valid_reply(reply):
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 | 
			
		||||
    kpro = KeyframeProClient()
 | 
			
		||||
    if kpro.connect():
 | 
			
		||||
        print("Connected successfully.")
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from . import *
 | 
			
		||||
@@ -0,0 +1,554 @@
 | 
			
		||||
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.0.1"
 | 
			
		||||
 | 
			
		||||
    KEYFRAME_PRO_PATH = "C:/Program Files/Keyframe Pro/bin/KeyframePro.exe"
 | 
			
		||||
    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.update_sync_state()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def update_sync_time(cls):
 | 
			
		||||
        if cls.is_initialized():
 | 
			
		||||
            frame = cmds.currentTime(q=True) + cls.get_sync_offset()
 | 
			
		||||
 | 
			
		||||
            if cls.get_from_range_start():
 | 
			
		||||
                range = cls.kpro_client.get_range()
 | 
			
		||||
                if range:
 | 
			
		||||
                    frame += range[0] - 1
 | 
			
		||||
 | 
			
		||||
            cls.kpro_client.set_frame(frame)
 | 
			
		||||
 | 
			
		||||
    @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":
 | 
			
		||||
            ext = "mov"
 | 
			
		||||
        else:
 | 
			
		||||
            om.MGlobal.displayError("Current playblast format is image. Images are not supported in the current version of Keyframe Pro")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        temp_dir = cls.get_temp_dir()
 | 
			
		||||
        if not temp_dir:
 | 
			
		||||
            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")
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        file_path = cmds.playblast(**args)
 | 
			
		||||
        om.MGlobal.displayInfo(file_path)
 | 
			
		||||
 | 
			
		||||
        # Open in viewer
 | 
			
		||||
        viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, q=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)
 | 
			
		||||
 | 
			
		||||
    @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')
 | 
			
		||||
            else:
 | 
			
		||||
                om.MGlobal.displayError("Open temp dir is not supported on the current platform ({0})".format(sys.platform))
 | 
			
		||||
 | 
			
		||||
    @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))
 | 
			
		||||
 | 
			
		||||
    @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:
 | 
			
		||||
            om.MGlobal.displayWarning("Unable to get temporary directory.")
 | 
			
		||||
 | 
			
		||||
        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 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=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()"),
 | 
			
		||||
                                           ec=lambda *args: 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=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()"),
 | 
			
		||||
                                             ec=lambda *args: 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=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()"),
 | 
			
		||||
                                                ec=lambda *args: 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=lambda *args: 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=lambda *args: MayaToKeyframePro.update_from_range_start(),
 | 
			
		||||
                                                     parent=sync_form_layout)
 | 
			
		||||
 | 
			
		||||
        sync_offset_to_current_btn = cmds.button(label="Current",
 | 
			
		||||
                                                 bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                                 c=lambda *args: MayaToKeyframePro.sync_offset_to_current(),
 | 
			
		||||
                                                 parent=sync_form_layout)
 | 
			
		||||
 | 
			
		||||
        reset_sync_offset_btn = cmds.button(label="  Reset  ",
 | 
			
		||||
                                            bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                            c=lambda *args: MayaToKeyframePro.set_sync_offset(0),
 | 
			
		||||
                                            parent=sync_form_layout)
 | 
			
		||||
 | 
			
		||||
        cls.sync_btn = cmds.button(label="SYNC", c=lambda *args: 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=lambda *args: MayaToKeyframePro.set_viewer_layout('single'),
 | 
			
		||||
                                        parent=viewer_form_layout)
 | 
			
		||||
 | 
			
		||||
        hori_viewer_btn = cmds.button(label="Horizontal",
 | 
			
		||||
                                      bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                      c=lambda *args: MayaToKeyframePro.set_viewer_layout('horizontal'),
 | 
			
		||||
                                      parent=viewer_form_layout)
 | 
			
		||||
 | 
			
		||||
        vert_viewer_btn = cmds.button(label=" Vertical ",
 | 
			
		||||
                                      bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                      c=lambda *args: MayaToKeyframePro.set_viewer_layout('vertical'),
 | 
			
		||||
                                      parent=viewer_form_layout)
 | 
			
		||||
 | 
			
		||||
        swap_timelines_btn = cmds.button(label="Swap Timelines",
 | 
			
		||||
                                         bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                         c=lambda *args: 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=lambda *args: MayaToKeyframePro.playblast(),  
 | 
			
		||||
                                    parent=playblast_form_layout)
 | 
			
		||||
 | 
			
		||||
        open_temp_dir_btn = cmds.button(label="Open Temp Folder",
 | 
			
		||||
                                        bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                        c=lambda *args: MayaToKeyframePro.open_temp_dir(),  
 | 
			
		||||
                                        parent=playblast_form_layout)
 | 
			
		||||
 | 
			
		||||
        clear_temp_dir_btn = cmds.button(label="Clear Temp Folder",
 | 
			
		||||
                                         bgc=cls.BUTTON_COLOR_01,
 | 
			
		||||
                                         c=lambda *args: 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()
 | 
			
		||||
		Reference in New Issue
	
	Block a user