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