From 65fa8aa90476db7d1d803574bafd712c7d2a10d6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 20 Dec 2021 10:36:56 +0100 Subject: [PATCH 01/62] added validation of installed third party libraries before build --- setup.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cd3ed4f82c..92cc76dc7a 100644 --- a/setup.py +++ b/setup.py @@ -3,15 +3,65 @@ import os import sys import re +import platform +import distutils.spawn from pathlib import Path from cx_Freeze import setup, Executable from sphinx.setup_command import BuildDoc -version = {} - openpype_root = Path(os.path.dirname(__file__)) + +def validate_thirdparty_binaries(): + """Check existence of thirdpart executables.""" + low_platform = platform.system().lower() + binary_vendors_dir = os.path.join( + openpype_root, + "vendor", + "bin" + ) + + error_msg = ( + "Missing binary dependency {}. Please fetch thirdparty dependencies." + ) + # Validate existence of FFmpeg + ffmpeg_dir = os.path.join(binary_vendors_dir, "ffmpeg", low_platform) + if low_platform == "windows": + ffmpeg_dir = os.path.join(ffmpeg_dir, "bin") + ffmpeg_executable = os.path.join(ffmpeg_dir, "ffmpeg") + ffmpeg_result = distutils.spawn.find_executable(ffmpeg_executable) + if ffmpeg_result is None: + raise RuntimeError(error_msg.format("FFmpeg")) + + # Validate existence of OpenImageIO (not on MacOs) + oiio_tool_path = None + if low_platform == "linux": + oiio_tool_path = os.path.join( + binary_vendors_dir, + "oiio", + low_platform, + "bin", + "oiiotool" + ) + elif low_platform == "windows": + oiio_tool_path = os.path.join( + binary_vendors_dir, + "oiio", + low_platform, + "oiiotool" + ) + oiio_result = None + if oiio_tool_path is not None: + oiio_result = distutils.spawn.find_executable(oiio_tool_path) + if oiio_result is None: + raise RuntimeError(error_msg.format("OpenImageIO")) + + +validate_thirdparty_binaries() + +version = {} + with open(openpype_root / "openpype" / "version.py") as fp: exec(fp.read(), version) From 1e49b7c87c4d8adad4c6b468ee9adbb8c391af4a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Dec 2021 14:45:51 +0100 Subject: [PATCH 02/62] Do not keep fixed geometry vertices selected/active after repair --- .../hosts/maya/plugins/publish/validate_shape_zero.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py index 2c594ef5f3..acc42f073a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py @@ -4,6 +4,8 @@ import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from avalon.maya import maintained_selection + class ValidateShapeZero(pyblish.api.Validator): """shape can't have any values @@ -47,8 +49,12 @@ class ValidateShapeZero(pyblish.api.Validator): @classmethod def repair(cls, instance): invalid_shapes = cls.get_invalid(instance) - for shape in invalid_shapes: - cmds.polyCollapseTweaks(shape) + if not invalid_shapes: + return + + with maintained_selection(): + for shape in invalid_shapes: + cmds.polyCollapseTweaks(shape) def process(self, instance): """Process all the nodes in the instance "objectSet""" From 73400612435880f731230a18afa603b0ad05df3b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Dec 2021 17:31:07 +0100 Subject: [PATCH 03/62] Fix repair taking very long time for many heavy meshes (optimization) --- .../hosts/maya/plugins/publish/validate_shape_zero.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py index acc42f073a..bb601b8f50 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib from avalon.maya import maintained_selection @@ -53,8 +54,13 @@ class ValidateShapeZero(pyblish.api.Validator): return with maintained_selection(): - for shape in invalid_shapes: - cmds.polyCollapseTweaks(shape) + with lib.tool("selectSuperContext"): + for shape in invalid_shapes: + cmds.polyCollapseTweaks(shape) + # cmds.polyCollapseTweaks keeps selecting the geometry + # after each command. When running on many meshes + # after one another this tends to get really heavy + cmds.select(clear=True) def process(self, instance): """Process all the nodes in the instance "objectSet""" From c4d91fb9c0e2fb6a57600c9dc18b319903735994 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Dec 2021 17:34:18 +0100 Subject: [PATCH 04/62] Improve docstring and error message - Previously it said something about translate, rotate and scale. However this validator doesn't check that at all and thus the docstring was incorrect. --- .../hosts/maya/plugins/publish/validate_shape_zero.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py index bb601b8f50..6b5c5d1398 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py @@ -9,11 +9,9 @@ from avalon.maya import maintained_selection class ValidateShapeZero(pyblish.api.Validator): - """shape can't have any values + """Shape components may not have any "tweak" values - To solve this issue, try freezing the shapes. So long - as the translation, rotation and scaling values are zero, - you're all good. + To solve this issue, try freezing the shapes. """ @@ -67,5 +65,5 @@ class ValidateShapeZero(pyblish.api.Validator): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Nodes found with shape or vertices not freezed" - "values: {0}".format(invalid)) + raise ValueError("Shapes found with non-zero component tweaks: " + "{0}".format(invalid)) From 9de27a8b04b1b16dbf0b1ca32b308b0d8ef46122 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 7 Jan 2022 12:04:15 +0100 Subject: [PATCH 05/62] OP-2204 - added new setting for upload review in Slack notification --- .../schemas/projects_schema/schema_project_slack.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json index 9ca4e443bd..4e82c991e7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -91,6 +91,11 @@ "key": "upload_thumbnail", "label": "Upload thumbnail" }, + { + "type": "boolean", + "key": "upload_review", + "label": "Upload review" + }, { "type": "text", "multiline": true, From 2229e19caa5c3fd90ce4b8ab1516c56bbd64d3ee Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 7 Jan 2022 12:06:33 +0100 Subject: [PATCH 06/62] OP-2204 - added possibility to upload or add link to review to Slack notification --- .../plugins/publish/integrate_slack_api.py | 132 ++++++++++++------ 1 file changed, 87 insertions(+), 45 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 7b81d3c364..b9f4b9d81f 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -14,6 +14,8 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): Project settings > Slack > Publish plugins > Notification to Slack. If instance contains 'thumbnail' it uploads it. Bot must be present in the target channel. + If instance contains 'review' it could upload (if configured) or place + link with {review_link} placeholder. Message template can contain {} placeholders from anatomyData. """ order = pyblish.api.IntegratorOrder + 0.499 @@ -23,44 +25,68 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): optional = True def process(self, instance): - published_path = self._get_thumbnail_path(instance) + thumbnail_path = self._get_thumbnail_path(instance) + review_path = self._get_review_path(instance) + publish_files = set() for message_profile in instance.data["slack_channel_message_profiles"]: message = self._get_filled_message(message_profile["message"], - instance) + instance, + review_path) if not message: return + if message_profile["upload_thumbnail"] and thumbnail_path: + publish_files.add(thumbnail_path) + + if message_profile["upload_review"] and review_path: + publish_files.add(review_path) + for channel in message_profile["channels"]: if six.PY2: self._python2_call(instance.data["slack_token"], channel, message, - published_path, - message_profile["upload_thumbnail"]) + publish_files) else: self._python3_call(instance.data["slack_token"], channel, message, - published_path, - message_profile["upload_thumbnail"]) + publish_files) - def _get_filled_message(self, message_templ, instance): - """Use message_templ and data from instance to get message content.""" + def _get_filled_message(self, message_templ, instance, review_path=None): + """Use message_templ and data from instance to get message content. + + Reviews might be large, so allow only adding link to message instead of + uploading only. + """ fill_data = copy.deepcopy(instance.context.data["anatomyData"]) - fill_pairs = ( + fill_pairs = [ ("asset", instance.data.get("asset", fill_data.get("asset"))), ("subset", instance.data.get("subset", fill_data.get("subset"))), - ("task", instance.data.get("task", fill_data.get("task"))), ("username", instance.data.get("username", fill_data.get("username"))), ("app", instance.data.get("app", fill_data.get("app"))), ("family", instance.data.get("family", fill_data.get("family"))), ("version", str(instance.data.get("version", fill_data.get("version")))) - ) + ] + if review_path: + fill_pairs.append(("review_link", review_path)) + task_on_instance = instance.data.get("task") + task_on_anatomy = fill_data.get("task") + if task_on_instance: + fill_pairs.append(("task[name]", task_on_instance.get("type"))) + fill_pairs.append(("task[name]", task_on_instance.get("name"))) + fill_pairs.append(("task[short]", task_on_instance.get("short"))) + elif task_on_anatomy: + fill_pairs.append(("task[name]", task_on_anatomy.get("type"))) + fill_pairs.append(("task[name]", task_on_anatomy.get("name"))) + fill_pairs.append(("task[short]", task_on_anatomy.get("short"))) + + self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) fill_data.update(multiple_case_variants) @@ -79,39 +105,51 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): published_path = None for repre in instance.data['representations']: if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_files = repre["files"] - if isinstance(repre_files, (tuple, list, set)): - filename = repre_files[0] - else: - filename = repre_files - - published_path = os.path.join( - repre['stagingDir'], filename - ) + if os.path.exists(repre["published_path"]): + published_path = repre["published_path"] break return published_path + def _get_review_path(self, instance): + """Returns abs url for review if present in instance repres""" + published_path = None + for repre in instance.data['representations']: + tags = repre.get('tags', []) + if (repre.get("review") + or "review" in tags + or "burnin" in tags): + if os.path.exists(repre["published_path"]): + published_path = repre["published_path"] + if "burnin" in tags: # burnin has precedence if exists + break + return published_path + def _python2_call(self, token, channel, message, - published_path, upload_thumbnail): + publish_files): from slackclient import SlackClient try: client = SlackClient(token) - if upload_thumbnail and \ - published_path and os.path.exists(published_path): - with open(published_path, 'rb') as pf: + for p_file in publish_files: + attachment_str = "\n\n Attachment links: \n" + with open(p_file, 'rb') as pf: response = client.api_call( "files.upload", channels=channel, - initial_comment=message, file=pf, - title=os.path.basename(published_path) + title=os.path.basename(p_file), ) - else: - response = client.api_call( - "chat.postMessage", - channel=channel, - text=message - ) + attachment_str += "\n<{}|{}>".format( + response["file"]["permalink"], + os.path.basename(p_file)) + + if publish_files: + message += attachment_str + + response = client.api_call( + "chat.postMessage", + channel=channel, + text=message + ) if response.get("error"): error_str = self._enrich_error(str(response.get("error")), @@ -123,23 +161,27 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): self.log.warning("Error happened: {}".format(error_str)) def _python3_call(self, token, channel, message, - published_path, upload_thumbnail): + publish_files): from slack_sdk import WebClient from slack_sdk.errors import SlackApiError try: client = WebClient(token=token) - if upload_thumbnail and \ - published_path and os.path.exists(published_path): - _ = client.files_upload( - channels=channel, - initial_comment=message, - file=published_path, - ) - else: - _ = client.chat_postMessage( - channel=channel, - text=message - ) + attachment_str = "\n\n Attachment links: \n" + for published_file in publish_files: + response = client.files_upload( + file=published_file, + filename=os.path.basename(published_file)) + attachment_str += "\n<{}|{}>".format( + response["file"]["permalink"], + os.path.basename(published_file)) + + if publish_files: + message += attachment_str + + _ = client.chat_postMessage( + channel=channel, + text=message + ) except SlackApiError as e: # You will get a SlackApiError if "ok" is False error_str = self._enrich_error(str(e.response["error"]), channel) From bdd7d159bac52d598f2eaa674813664402594e0d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 7 Jan 2022 12:07:35 +0100 Subject: [PATCH 07/62] OP-2204 - added documentation to Slack section --- website/docs/module_slack.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index f71fcc2bb7..10d5e58eac 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -61,6 +61,18 @@ Integration can upload 'thumbnail' file (if present in an instance), for that bo manually added to target channel by Slack admin! (In target channel write: ```/invite @OpenPypeNotifier``) +#### Upload review +Integration can upload 'review' file (if present in an instance), for that bot must be +manually added to target channel by Slack admin! +(In target channel write: ```/invite @OpenPypeNotifier``) + +Burnin version (usually .mp4) is preferred if present. + +Please be sure that this configuration is viable for your use case. In case of uploading large reviews to Slack, +all publishes will be slowed down and you might hit a file limit on Slack pretty soon (it is 5GB for Free version of Slack, any file cannot be bigger than 1GB). +You might try to add `{review_link}` to message content. This link might help users to find review easier on their machines. +(It won't show a playable preview though!) + #### Message Message content can use Templating (see [Available template keys](admin_settings_project_anatomy#available-template-keys)). @@ -69,8 +81,22 @@ Few keys also have Capitalized and UPPERCASE format. Values will be modified acc **Available keys:** - asset - subset -- task +- task\[name\] +- task\[type\] +- task\[short\] - username - app - family - version +- review_link + +##### Message example +``` +{Subset} was published for {ASSET} in {task[name]} task. + +Here you can find review {review_link} +``` + +#### Message retention +Currently no purging of old messages is implemented in Openpype. Admins of Slack should set their own retention of messages and files per channel. +(see https://slack.com/help/articles/203457187-Customize-message-and-file-retention-policies) From b12b27e505619a115d641eb09dba365539f5b7a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 7 Jan 2022 14:40:00 +0100 Subject: [PATCH 08/62] OP-2204 - added customization of bot appearance --- openpype/modules/slack/manifest.yml | 1 + .../slack/plugins/publish/integrate_slack_api.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 37d4669903..bd920ac266 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -15,6 +15,7 @@ oauth_config: scopes: bot: - chat:write + - chat:write.customize - chat:write.public - files:write settings: diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index b9f4b9d81f..dd2d4ca048 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -24,6 +24,10 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): optional = True + # internal, not configurable + bot_user_name = "OpenpypeNotifier" + icon_url = "https://openpype.io/img/favicon/favicon.ico" + def process(self, instance): thumbnail_path = self._get_thumbnail_path(instance) review_path = self._get_review_path(instance) @@ -148,7 +152,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): response = client.api_call( "chat.postMessage", channel=channel, - text=message + text=message, + username=self.bot_user_name, + icon_url=self.icon_url ) if response.get("error"): @@ -180,7 +186,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): _ = client.chat_postMessage( channel=channel, - text=message + text=message, + username=self.bot_user_name, + icon_url=self.icon_url ) except SlackApiError as e: # You will get a SlackApiError if "ok" is False From 6ca2ae704bbc76e61dbc439cff23dee8cb25f85d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 7 Jan 2022 14:50:17 +0100 Subject: [PATCH 09/62] Update openpype/modules/slack/plugins/publish/integrate_slack_api.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/integrate_slack_api.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index b9f4b9d81f..e094c268da 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -75,16 +75,13 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if review_path: fill_pairs.append(("review_link", review_path)) - task_on_instance = instance.data.get("task") - task_on_anatomy = fill_data.get("task") - if task_on_instance: - fill_pairs.append(("task[name]", task_on_instance.get("type"))) - fill_pairs.append(("task[name]", task_on_instance.get("name"))) - fill_pairs.append(("task[short]", task_on_instance.get("short"))) - elif task_on_anatomy: - fill_pairs.append(("task[name]", task_on_anatomy.get("type"))) - fill_pairs.append(("task[name]", task_on_anatomy.get("name"))) - fill_pairs.append(("task[short]", task_on_anatomy.get("short"))) + task_data = instance.data.get("task") + if not task_data: + task_data = fill_data.get("task") + for key, value in task_data.items(): + fill_key = "task[{}]".format(key) + fill_pairs.append((fill_key , value)) + fill_pairs.append(("task", task_data["name"])) self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) From 69b9c06f6a79236ea1c9ceeaee83ef79db5a29fd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 7 Jan 2022 14:57:32 +0100 Subject: [PATCH 10/62] OP-2204 - renamed placeholder to review_filepath --- .../slack/plugins/publish/integrate_slack_api.py | 4 ++-- website/docs/module_slack.md | 15 +++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index e094c268da..cdc90a7a28 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -15,7 +15,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): If instance contains 'thumbnail' it uploads it. Bot must be present in the target channel. If instance contains 'review' it could upload (if configured) or place - link with {review_link} placeholder. + link with {review_filepath} placeholder. Message template can contain {} placeholders from anatomyData. """ order = pyblish.api.IntegratorOrder + 0.499 @@ -73,7 +73,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): fill_data.get("version")))) ] if review_path: - fill_pairs.append(("review_link", review_path)) + fill_pairs.append(("review_filepath", review_path)) task_data = instance.data.get("task") if not task_data: diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 10d5e58eac..d74ff3a290 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -78,23 +78,14 @@ Message content can use Templating (see [Available template keys](admin_settings Few keys also have Capitalized and UPPERCASE format. Values will be modified accordingly ({Asset} >> "Asset", {FAMILY} >> "RENDER"). -**Available keys:** -- asset -- subset -- task\[name\] -- task\[type\] -- task\[short\] -- username -- app -- family -- version -- review_link +**Additional implemented keys:** +- review_filepath ##### Message example ``` {Subset} was published for {ASSET} in {task[name]} task. -Here you can find review {review_link} +Here you can find review {review_filepath} ``` #### Message retention From 1be9a4112a7baff6ae91f324f048b5af849bb32a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 9 Jan 2022 20:42:40 +0100 Subject: [PATCH 11/62] Improve FusionPreLaunch hook error readability + make it a pop-up from the launcher. - I've removed the usage of ` in the string as they would convert into special characters in the pop-up. So those are changed to '. --- .../hosts/fusion/hooks/pre_fusion_setup.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index a0c16a6700..9da7237505 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,6 +1,6 @@ import os import importlib -from openpype.lib import PreLaunchHook +from openpype.lib import PreLaunchHook, ApplicationLaunchFailed from openpype.hosts.fusion.api import utils @@ -14,24 +14,26 @@ class FusionPrelaunch(PreLaunchHook): def execute(self): # making sure pyton 3.6 is installed at provided path py36_dir = os.path.normpath(self.launch_context.env.get("PYTHON36", "")) - assert os.path.isdir(py36_dir), ( - "Python 3.6 is not installed at the provided folder path. Either " - "make sure the `environments\resolve.json` is having correctly " - "set `PYTHON36` or make sure Python 3.6 is installed " - f"in given path. \nPYTHON36E: `{py36_dir}`" + if not os.path.isdir(py36_dir): + raise ApplicationLaunchFailed( + "Python 3.6 is not installed at the provided path.\n" + "Either make sure the 'environments/fusion.json' has " + "'PYTHON36' set corectly or make sure Python 3.6 is installed " + f"in the given path.\n\nPYTHON36: {py36_dir}" ) - self.log.info(f"Path to Fusion Python folder: `{py36_dir}`...") + self.log.info(f"Path to Fusion Python folder: '{py36_dir}'...") self.launch_context.env["PYTHON36"] = py36_dir # setting utility scripts dir for scripts syncing us_dir = os.path.normpath( self.launch_context.env.get("FUSION_UTILITY_SCRIPTS_DIR", "") ) - assert os.path.isdir(us_dir), ( - "Fusion utility script dir does not exists. Either make sure " - "the `environments\fusion.json` is having correctly set " - "`FUSION_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" - f"FUSION_UTILITY_SCRIPTS_DIR: `{us_dir}`" + if not os.path.isdir(us_dir): + raise ApplicationLaunchFailed( + "Fusion utility script dir does not exist. Either make sure " + "the 'environments/fusion.json' has 'FUSION_UTILITY_SCRIPTS_DIR' " + "set correctly or reinstall DaVinci Resolve.\n\n" + f"FUSION_UTILITY_SCRIPTS_DIR: '{us_dir}'" ) try: From ff8643a128e57bb72ad42c8e31ad9925026c2e81 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 9 Jan 2022 20:48:39 +0100 Subject: [PATCH 12/62] Fix indentations --- .../hosts/fusion/hooks/pre_fusion_setup.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 9da7237505..906c1e7b8a 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -16,11 +16,11 @@ class FusionPrelaunch(PreLaunchHook): py36_dir = os.path.normpath(self.launch_context.env.get("PYTHON36", "")) if not os.path.isdir(py36_dir): raise ApplicationLaunchFailed( - "Python 3.6 is not installed at the provided path.\n" - "Either make sure the 'environments/fusion.json' has " - "'PYTHON36' set corectly or make sure Python 3.6 is installed " - f"in the given path.\n\nPYTHON36: {py36_dir}" - ) + "Python 3.6 is not installed at the provided path.\n" + "Either make sure the 'environments/fusion.json' has " + "'PYTHON36' set corectly or make sure Python 3.6 is installed " + f"in the given path.\n\nPYTHON36: {py36_dir}" + ) self.log.info(f"Path to Fusion Python folder: '{py36_dir}'...") self.launch_context.env["PYTHON36"] = py36_dir @@ -30,11 +30,12 @@ class FusionPrelaunch(PreLaunchHook): ) if not os.path.isdir(us_dir): raise ApplicationLaunchFailed( - "Fusion utility script dir does not exist. Either make sure " - "the 'environments/fusion.json' has 'FUSION_UTILITY_SCRIPTS_DIR' " - "set correctly or reinstall DaVinci Resolve.\n\n" - f"FUSION_UTILITY_SCRIPTS_DIR: '{us_dir}'" - ) + "Fusion utility script dir does not exist. Either make sure " + "the 'environments/fusion.json' has " + "'FUSION_UTILITY_SCRIPTS_DIR' set correctly or reinstall " + "DaVinci Resolve.\n\n" + f"FUSION_UTILITY_SCRIPTS_DIR: '{us_dir}'" + ) try: __import__("avalon.fusion") From 425dbad2ac33cdcb960aa1ed539f2caf9532543e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 9 Jan 2022 20:49:24 +0100 Subject: [PATCH 13/62] Refactor mention of Resolve to Fusion. --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 906c1e7b8a..8c4973cf43 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -33,8 +33,7 @@ class FusionPrelaunch(PreLaunchHook): "Fusion utility script dir does not exist. Either make sure " "the 'environments/fusion.json' has " "'FUSION_UTILITY_SCRIPTS_DIR' set correctly or reinstall " - "DaVinci Resolve.\n\n" - f"FUSION_UTILITY_SCRIPTS_DIR: '{us_dir}'" + f"Fusion.\n\nFUSION_UTILITY_SCRIPTS_DIR: '{us_dir}'" ) try: From 5d2c5d7776b68de2b7f37a262f69e16c7f4c4071 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 9 Jan 2022 20:55:14 +0100 Subject: [PATCH 14/62] Fix #2497: reset empty string attributes correctly to "" instead of "None" --- openpype/hosts/maya/api/lib.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 52ebcaff64..ac22cdc777 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -313,13 +313,7 @@ def attribute_values(attr_values): """ - # NOTE(antirotor): this didn't work for some reason for Yeti attributes - # original = [(attr, cmds.getAttr(attr)) for attr in attr_values] - original = [] - for attr in attr_values: - type = cmds.getAttr(attr, type=True) - value = cmds.getAttr(attr) - original.append((attr, str(value) if type == "string" else value)) + original = [(attr, cmds.getAttr(attr)) for attr in attr_values] try: for attr, value in attr_values.items(): if isinstance(value, string_types): @@ -331,6 +325,12 @@ def attribute_values(attr_values): for attr, value in original: if isinstance(value, string_types): cmds.setAttr(attr, value, type="string") + elif value is None and cmds.getAttr(attr, type=True) == "string": + # In some cases the maya.cmds.getAttr command returns None + # for string attributes but this value cannot assigned. + # Note: After setting it once to "" it will then return "" + # instead of None. So this would only happen once. + cmds.setAttr(attr, "", type="string") else: cmds.setAttr(attr, value) From b66f6b95bb46dbaf671ca70e16c506943fad88be Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 10 Jan 2022 12:00:42 +0100 Subject: [PATCH 15/62] OP-2205 - working version for upload multiple files Won't be used probably as purging of old files would be impossible in this use case. --- .../plugins/publish/integrate_slack_api.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index d7be0c0bfa..5aba372549 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -25,7 +25,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): optional = True # internal, not configurable - bot_user_name = "OpenpypeNotifier" + bot_user_name = "OpenPypeNotifier" icon_url = "https://openpype.io/img/favicon/favicon.ico" def process(self, instance): @@ -37,11 +37,12 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): message = self._get_filled_message(message_profile["message"], instance, review_path) + self.log.info("message:: {}".format(message)) if not message: return - if message_profile["upload_thumbnail"] and thumbnail_path: - publish_files.add(thumbnail_path) + # if message_profile["upload_thumbnail"] and thumbnail_path: + # publish_files.add(thumbnail_path) if message_profile["upload_review"] and review_path: publish_files.add(review_path) @@ -130,14 +131,14 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): from slackclient import SlackClient try: client = SlackClient(token) + self.log.info("publish {}".format(publish_files)) + attachment_str = "\n\n Attachment links: \n" for p_file in publish_files: - attachment_str = "\n\n Attachment links: \n" with open(p_file, 'rb') as pf: response = client.api_call( "files.upload", channels=channel, - file=pf, - title=os.path.basename(p_file), + file=pf ) attachment_str += "\n<{}|{}>".format( response["file"]["permalink"], @@ -149,11 +150,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): response = client.api_call( "chat.postMessage", channel=channel, - text=message, - username=self.bot_user_name, - icon_url=self.icon_url + text=message ) - + self.log.info("repsonse {}".format(response)) if response.get("error"): error_str = self._enrich_error(str(response.get("error")), channel) From e4368e69b1088ea3345932b9109a20a5c0d83de7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Jan 2022 12:25:50 +0100 Subject: [PATCH 16/62] moved nuke implementation from avalon --- openpype/hosts/nuke/api/__init__.py | 164 ++--- openpype/hosts/nuke/api/actions.py | 5 +- openpype/hosts/nuke/api/command.py | 135 ++++ openpype/hosts/nuke/api/lib.py | 616 ++++++++++++++++-- openpype/hosts/nuke/api/menu.py | 166 ----- openpype/hosts/nuke/api/pipeline.py | 421 ++++++++++++ openpype/hosts/nuke/api/plugin.py | 67 +- openpype/hosts/nuke/api/utils.py | 5 +- openpype/hosts/nuke/api/workio.py | 55 ++ .../nuke/plugins/create/create_backdrop.py | 15 +- .../nuke/plugins/create/create_camera.py | 12 +- .../hosts/nuke/plugins/create/create_gizmo.py | 26 +- .../hosts/nuke/plugins/create/create_model.py | 12 +- .../hosts/nuke/plugins/create/create_read.py | 15 +- .../plugins/create/create_write_prerender.py | 11 +- .../plugins/create/create_write_render.py | 11 +- .../nuke/plugins/create/create_write_still.py | 11 +- .../plugins/inventory/repair_old_loaders.py | 9 +- .../plugins/inventory/select_containers.py | 4 +- .../hosts/nuke/plugins/load/load_backdrop.py | 40 +- .../nuke/plugins/load/load_camera_abc.py | 18 +- openpype/hosts/nuke/plugins/load/load_clip.py | 13 +- .../hosts/nuke/plugins/load/load_effects.py | 17 +- .../nuke/plugins/load/load_effects_ip.py | 17 +- .../hosts/nuke/plugins/load/load_gizmo.py | 23 +- .../hosts/nuke/plugins/load/load_gizmo_ip.py | 31 +- .../hosts/nuke/plugins/load/load_image.py | 17 +- .../hosts/nuke/plugins/load/load_model.py | 15 +- .../nuke/plugins/load/load_script_precomp.py | 17 +- .../nuke/plugins/publish/extract_backdrop.py | 25 +- .../nuke/plugins/publish/extract_camera.py | 10 +- .../nuke/plugins/publish/extract_gizmo.py | 20 +- .../nuke/plugins/publish/extract_model.py | 13 +- .../plugins/publish/extract_ouput_node.py | 2 +- .../publish/extract_review_data_lut.py | 6 +- .../publish/extract_review_data_mov.py | 6 +- .../plugins/publish/extract_slate_frame.py | 4 +- .../nuke/plugins/publish/extract_thumbnail.py | 4 +- .../plugins/publish/precollect_instances.py | 9 +- .../plugins/publish/precollect_workfile.py | 15 +- .../nuke/plugins/publish/validate_backdrop.py | 6 +- .../nuke/plugins/publish/validate_gizmo.py | 6 +- .../publish/validate_instance_in_context.py | 13 +- .../plugins/publish/validate_write_legacy.py | 5 +- .../plugins/publish/validate_write_nodes.py | 15 +- openpype/hosts/nuke/startup/init.py | 2 + openpype/hosts/nuke/startup/menu.py | 15 +- 47 files changed, 1581 insertions(+), 563 deletions(-) create mode 100644 openpype/hosts/nuke/api/command.py delete mode 100644 openpype/hosts/nuke/api/menu.py create mode 100644 openpype/hosts/nuke/api/pipeline.py create mode 100644 openpype/hosts/nuke/api/workio.py diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 1567189ed1..d3b7f74d6d 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -1,130 +1,52 @@ -import os -import nuke +from .workio import ( + file_extensions, + has_unsaved_changes, + save_file, + open_file, + current_file, + work_root, +) -import avalon.api -import pyblish.api -import openpype -from . import lib, menu +from .command import ( + reset_frame_range, + get_handles, + reset_resolution, + viewer_update_and_undo_stop +) -log = openpype.api.Logger().get_logger(__name__) +from .plugin import OpenPypeCreator +from .pipeline import ( + install, + uninstall, -AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.nuke.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + ls, + + containerise, + parse_container, + update_container, +) -# registering pyblish gui regarding settings in presets -if os.getenv("PYBLISH_GUI", None): - pyblish.api.register_gui(os.getenv("PYBLISH_GUI", None)) +__all__ = ( + "file_extensions", + "has_unsaved_changes", + "save_file", + "open_file", + "current_file", + "work_root", + "reset_frame_range", + "get_handles", + "reset_resolution", + "viewer_update_and_undo_stop", -def reload_config(): - """Attempt to reload pipeline at run-time. + "OpenPypeCreator", + "install", + "uninstall", - CAUTION: This is primarily for development and debugging purposes. + "ls", - """ - - import importlib - - for module in ( - "{}.api".format(AVALON_CONFIG), - "{}.hosts.nuke.api.actions".format(AVALON_CONFIG), - "{}.hosts.nuke.api.menu".format(AVALON_CONFIG), - "{}.hosts.nuke.api.plugin".format(AVALON_CONFIG), - "{}.hosts.nuke.api.lib".format(AVALON_CONFIG), - ): - log.info("Reloading module: {}...".format(module)) - - module = importlib.import_module(module) - - try: - importlib.reload(module) - except AttributeError as e: - from importlib import reload - log.warning("Cannot reload module: {}".format(e)) - reload(module) - - -def install(): - ''' Installing all requarements for Nuke host - ''' - - # remove all registred callbacks form avalon.nuke - from avalon import pipeline - pipeline._registered_event_handlers.clear() - - log.info("Registering Nuke plug-ins..") - pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) - avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH) - avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) - - # Register Avalon event for workfiles loading. - avalon.api.on("workio.open_file", lib.check_inventory_versions) - avalon.api.on("taskChanged", menu.change_context_label) - - pyblish.api.register_callback( - "instanceToggled", on_pyblish_instance_toggled) - workfile_settings = lib.WorkfileSettings() - # Disable all families except for the ones we explicitly want to see - family_states = [ - "write", - "review", - "nukenodes", - "model", - "gizmo" - ] - - avalon.api.data["familiesStateDefault"] = False - avalon.api.data["familiesStateToggled"] = family_states - - # Set context settings. - nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root") - nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root") - nuke.addOnCreate(lib.process_workfile_builder, nodeClass="Root") - nuke.addOnCreate(lib.launch_workfiles_app, nodeClass="Root") - menu.install() - - -def uninstall(): - '''Uninstalling host's integration - ''' - log.info("Deregistering Nuke plug-ins..") - pyblish.api.deregister_plugin_path(PUBLISH_PATH) - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) - avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH) - - pyblish.api.deregister_callback( - "instanceToggled", on_pyblish_instance_toggled) - - reload_config() - menu.uninstall() - - -def on_pyblish_instance_toggled(instance, old_value, new_value): - """Toggle node passthrough states on instance toggles.""" - - log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( - instance, old_value, new_value)) - - from avalon.nuke import ( - viewer_update_and_undo_stop, - add_publish_knob - ) - - # Whether instances should be passthrough based on new value - - with viewer_update_and_undo_stop(): - n = instance[0] - try: - n["publish"].value() - except ValueError: - n = add_publish_knob(n) - log.info(" `Publish` knob was added to write node..") - - n["publish"].setValue(new_value) + "containerise", + "parse_container", + "update_container", +) diff --git a/openpype/hosts/nuke/api/actions.py b/openpype/hosts/nuke/api/actions.py index fd18c787c4..c4a6f0fb84 100644 --- a/openpype/hosts/nuke/api/actions.py +++ b/openpype/hosts/nuke/api/actions.py @@ -1,12 +1,11 @@ import pyblish.api -from avalon.nuke.lib import ( +from openpype.api import get_errored_instances_from_context +from .lib import ( reset_selection, select_nodes ) -from openpype.api import get_errored_instances_from_context - class SelectInvalidAction(pyblish.api.Action): """Select invalid nodes in Nuke when plug-in failed. diff --git a/openpype/hosts/nuke/api/command.py b/openpype/hosts/nuke/api/command.py new file mode 100644 index 0000000000..212d4757c6 --- /dev/null +++ b/openpype/hosts/nuke/api/command.py @@ -0,0 +1,135 @@ +import logging +import contextlib +import nuke + +from avalon import api, io + + +log = logging.getLogger(__name__) + + +def reset_frame_range(): + """ Set frame range to current asset + Also it will set a Viewer range with + displayed handles + """ + + fps = float(api.Session.get("AVALON_FPS", 25)) + + nuke.root()["fps"].setValue(fps) + name = api.Session["AVALON_ASSET"] + asset = io.find_one({"name": name, "type": "asset"}) + asset_data = asset["data"] + + handles = get_handles(asset) + + frame_start = int(asset_data.get( + "frameStart", + asset_data.get("edit_in"))) + + frame_end = int(asset_data.get( + "frameEnd", + asset_data.get("edit_out"))) + + if not all([frame_start, frame_end]): + missing = ", ".join(["frame_start", "frame_end"]) + msg = "'{}' are not set for asset '{}'!".format(missing, name) + log.warning(msg) + nuke.message(msg) + return + + frame_start -= handles + frame_end += handles + + nuke.root()["first_frame"].setValue(frame_start) + nuke.root()["last_frame"].setValue(frame_end) + + # setting active viewers + vv = nuke.activeViewer().node() + vv["frame_range_lock"].setValue(True) + vv["frame_range"].setValue("{0}-{1}".format( + int(asset_data["frameStart"]), + int(asset_data["frameEnd"])) + ) + + +def get_handles(asset): + """ Gets handles data + + Arguments: + asset (dict): avalon asset entity + + Returns: + handles (int) + """ + data = asset["data"] + if "handles" in data and data["handles"] is not None: + return int(data["handles"]) + + parent_asset = None + if "visualParent" in data: + vp = data["visualParent"] + if vp is not None: + parent_asset = io.find_one({"_id": io.ObjectId(vp)}) + + if parent_asset is None: + parent_asset = io.find_one({"_id": io.ObjectId(asset["parent"])}) + + if parent_asset is not None: + return get_handles(parent_asset) + else: + return 0 + + +def reset_resolution(): + """Set resolution to project resolution.""" + project = io.find_one({"type": "project"}) + p_data = project["data"] + + width = p_data.get("resolution_width", + p_data.get("resolutionWidth")) + height = p_data.get("resolution_height", + p_data.get("resolutionHeight")) + + if not all([width, height]): + missing = ", ".join(["width", "height"]) + msg = "No resolution information `{0}` found for '{1}'.".format( + missing, + project["name"]) + log.warning(msg) + nuke.message(msg) + return + + current_width = nuke.root()["format"].value().width() + current_height = nuke.root()["format"].value().height() + + if width != current_width or height != current_height: + + fmt = None + for f in nuke.formats(): + if f.width() == width and f.height() == height: + fmt = f.name() + + if not fmt: + nuke.addFormat( + "{0} {1} {2}".format(int(width), int(height), project["name"]) + ) + fmt = project["name"] + + nuke.root()["format"].setValue(fmt) + + +@contextlib.contextmanager +def viewer_update_and_undo_stop(): + """Lock viewer from updating and stop recording undo steps""" + try: + # stop active viewer to update any change + viewer = nuke.activeViewer() + if viewer: + viewer.stop() + else: + log.warning("No available active Viewer") + nuke.Undo.disable() + yield + finally: + nuke.Undo.enable() diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index e36a5aa5ba..0508de9f1d 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3,15 +3,15 @@ import re import sys import six import platform +import contextlib from collections import OrderedDict +import clique + +import nuke from avalon import api, io, lib -import avalon.nuke -from avalon.nuke import lib as anlib -from avalon.nuke import ( - save_file, open_file -) + from openpype.api import ( Logger, Anatomy, @@ -28,21 +28,476 @@ from openpype.lib.path_tools import HostDirmap from openpype.settings import get_project_settings from openpype.modules import ModulesManager -import nuke +from .workio import ( + save_file, + open_file +) -from .utils import set_context_favorites +log = Logger.get_logger(__name__) -log = Logger().get_logger(__name__) +_NODE_TAB_NAME = "{}".format(os.getenv("AVALON_LABEL") or "Avalon") +AVALON_LABEL = os.getenv("AVALON_LABEL") or "Avalon" +AVALON_TAB = "{}".format(AVALON_LABEL) +AVALON_DATA_GROUP = "{}DataGroup".format(AVALON_LABEL.capitalize()) +EXCLUDED_KNOB_TYPE_ON_READ = ( + 20, # Tab Knob + 26, # Text Knob (But for backward compatibility, still be read + # if value is not an empty string.) +) -opnl = sys.modules[__name__] -opnl._project = None -opnl.project_name = os.getenv("AVALON_PROJECT") -opnl.workfiles_launched = False -opnl._node_tab_name = "{}".format(os.getenv("AVALON_LABEL") or "Avalon") + +class Context: + main_window = None + context_label = None + project_name = os.getenv("AVALON_PROJECT") + workfiles_launched = False + # Seems unused + _project_doc = None + + +class Knobby(object): + """For creating knob which it's type isn't mapped in `create_knobs` + + Args: + type (string): Nuke knob type name + value: Value to be set with `Knob.setValue`, put `None` if not required + flags (list, optional): Knob flags to be set with `Knob.setFlag` + *args: Args other than knob name for initializing knob class + + """ + + def __init__(self, type, value, flags=None, *args): + self.type = type + self.value = value + self.flags = flags or [] + self.args = args + + def create(self, name, nice=None): + knob_cls = getattr(nuke, self.type) + knob = knob_cls(name, nice, *self.args) + if self.value is not None: + knob.setValue(self.value) + for flag in self.flags: + knob.setFlag(flag) + return knob + + +def create_knobs(data, tab=None): + """Create knobs by data + + Depending on the type of each dict value and creates the correct Knob. + + Mapped types: + bool: nuke.Boolean_Knob + int: nuke.Int_Knob + float: nuke.Double_Knob + list: nuke.Enumeration_Knob + six.string_types: nuke.String_Knob + + dict: If it's a nested dict (all values are dict), will turn into + A tabs group. Or just a knobs group. + + Args: + data (dict): collection of attributes and their value + tab (string, optional): Knobs' tab name + + Returns: + list: A list of `nuke.Knob` objects + + """ + def nice_naming(key): + """Convert camelCase name into UI Display Name""" + words = re.findall('[A-Z][^A-Z]*', key[0].upper() + key[1:]) + return " ".join(words) + + # Turn key-value pairs into knobs + knobs = list() + + if tab: + knobs.append(nuke.Tab_Knob(tab)) + + for key, value in data.items(): + # Knob name + if isinstance(key, tuple): + name, nice = key + else: + name, nice = key, nice_naming(key) + + # Create knob by value type + if isinstance(value, Knobby): + knobby = value + knob = knobby.create(name, nice) + + elif isinstance(value, float): + knob = nuke.Double_Knob(name, nice) + knob.setValue(value) + + elif isinstance(value, bool): + knob = nuke.Boolean_Knob(name, nice) + knob.setValue(value) + knob.setFlag(nuke.STARTLINE) + + elif isinstance(value, int): + knob = nuke.Int_Knob(name, nice) + knob.setValue(value) + + elif isinstance(value, six.string_types): + knob = nuke.String_Knob(name, nice) + knob.setValue(value) + + elif isinstance(value, list): + knob = nuke.Enumeration_Knob(name, nice, value) + + elif isinstance(value, dict): + if all(isinstance(v, dict) for v in value.values()): + # Create a group of tabs + begain = nuke.BeginTabGroup_Knob() + end = nuke.EndTabGroup_Knob() + begain.setName(name) + end.setName(name + "_End") + knobs.append(begain) + for k, v in value.items(): + knobs += create_knobs(v, tab=k) + knobs.append(end) + else: + # Create a group of knobs + knobs.append(nuke.Tab_Knob( + name, nice, nuke.TABBEGINCLOSEDGROUP)) + knobs += create_knobs(value) + knobs.append( + nuke.Tab_Knob(name + "_End", nice, nuke.TABENDGROUP)) + continue + + else: + raise TypeError("Unsupported type: %r" % type(value)) + + knobs.append(knob) + + return knobs + + +def imprint(node, data, tab=None): + """Store attributes with value on node + + Parse user data into Node knobs. + Use `collections.OrderedDict` to ensure knob order. + + Args: + node(nuke.Node): node object from Nuke + data(dict): collection of attributes and their value + + Returns: + None + + Examples: + ``` + import nuke + from avalon.nuke import lib + + node = nuke.createNode("NoOp") + data = { + # Regular type of attributes + "myList": ["x", "y", "z"], + "myBool": True, + "myFloat": 0.1, + "myInt": 5, + + # Creating non-default imprint type of knob + "MyFilePath": lib.Knobby("File_Knob", "/file/path"), + "divider": lib.Knobby("Text_Knob", ""), + + # Manual nice knob naming + ("my_knob", "Nice Knob Name"): "some text", + + # dict type will be created as knob group + "KnobGroup": { + "knob1": 5, + "knob2": "hello", + "knob3": ["a", "b"], + }, + + # Nested dict will be created as tab group + "TabGroup": { + "tab1": {"count": 5}, + "tab2": {"isGood": True}, + "tab3": {"direction": ["Left", "Right"]}, + }, + } + lib.imprint(node, data, tab="Demo") + + ``` + + """ + for knob in create_knobs(data, tab): + node.addKnob(knob) + + +def add_publish_knob(node): + """Add Publish knob to node + + Arguments: + node (nuke.Node): nuke node to be processed + + Returns: + node (nuke.Node): processed nuke node + + """ + if "publish" not in node.knobs(): + body = OrderedDict() + body[("divd", "Publishing")] = Knobby("Text_Knob", '') + body["publish"] = True + imprint(node, body) + return node + + +def set_avalon_knob_data(node, data=None, prefix="avalon:"): + """ Sets data into nodes's avalon knob + + Arguments: + node (nuke.Node): Nuke node to imprint with data, + data (dict, optional): Data to be imprinted into AvalonTab + prefix (str, optional): filtering prefix + + Returns: + node (nuke.Node) + + Examples: + data = { + 'asset': 'sq020sh0280', + 'family': 'render', + 'subset': 'subsetMain' + } + """ + data = data or dict() + create = OrderedDict() + + tab_name = AVALON_TAB + editable = ["asset", "subset", "name", "namespace"] + + existed_knobs = node.knobs() + + for key, value in data.items(): + knob_name = prefix + key + gui_name = key + + if knob_name in existed_knobs: + # Set value + try: + node[knob_name].setValue(value) + except TypeError: + node[knob_name].setValue(str(value)) + else: + # New knob + name = (knob_name, gui_name) # Hide prefix on GUI + if key in editable: + create[name] = value + else: + create[name] = Knobby("String_Knob", + str(value), + flags=[nuke.READ_ONLY]) + if tab_name in existed_knobs: + tab_name = None + else: + tab = OrderedDict() + warn = Knobby("Text_Knob", "Warning! Do not change following data!") + divd = Knobby("Text_Knob", "") + head = [ + (("warn", ""), warn), + (("divd", ""), divd), + ] + tab[AVALON_DATA_GROUP] = OrderedDict(head + list(create.items())) + create = tab + + imprint(node, create, tab=tab_name) + return node + + +def get_avalon_knob_data(node, prefix="avalon:"): + """ Gets a data from nodes's avalon knob + + Arguments: + node (obj): Nuke node to search for data, + prefix (str, optional): filtering prefix + + Returns: + data (dict) + """ + + # check if lists + if not isinstance(prefix, list): + prefix = list([prefix]) + + data = dict() + + # loop prefix + for p in prefix: + # check if the node is avalon tracked + if AVALON_TAB not in node.knobs(): + continue + try: + # check if data available on the node + test = node[AVALON_DATA_GROUP].value() + log.debug("Only testing if data avalable: `{}`".format(test)) + except NameError as e: + # if it doesn't then create it + log.debug("Creating avalon knob: `{}`".format(e)) + node = set_avalon_knob_data(node) + return get_avalon_knob_data(node) + + # get data from filtered knobs + data.update({k.replace(p, ''): node[k].value() + for k in node.knobs().keys() + if p in k}) + + return data + + +def fix_data_for_node_create(data): + """Fixing data to be used for nuke knobs + """ + for k, v in data.items(): + if isinstance(v, six.text_type): + data[k] = str(v) + if str(v).startswith("0x"): + data[k] = int(v, 16) + return data + + +def add_write_node(name, **kwarg): + """Adding nuke write node + + Arguments: + name (str): nuke node name + kwarg (attrs): data for nuke knobs + + Returns: + node (obj): nuke write node + """ + frame_range = kwarg.get("frame_range", None) + + w = nuke.createNode( + "Write", + "name {}".format(name)) + + w["file"].setValue(kwarg["file"]) + + for k, v in kwarg.items(): + if "frame_range" in k: + continue + log.info([k, v]) + try: + w[k].setValue(v) + except KeyError as e: + log.debug(e) + continue + + if frame_range: + w["use_limit"].setValue(True) + w["first"].setValue(frame_range[0]) + w["last"].setValue(frame_range[1]) + + return w + + +def read(node): + """Return user-defined knobs from given `node` + + Args: + node (nuke.Node): Nuke node object + + Returns: + list: A list of nuke.Knob object + + """ + def compat_prefixed(knob_name): + if knob_name.startswith("avalon:"): + return knob_name[len("avalon:"):] + elif knob_name.startswith("ak:"): + return knob_name[len("ak:"):] + else: + return knob_name + + data = dict() + + pattern = ("(?<=addUserKnob {)" + "([0-9]*) (\\S*)" # Matching knob type and knob name + "(?=[ |}])") + tcl_script = node.writeKnobs(nuke.WRITE_USER_KNOB_DEFS) + result = re.search(pattern, tcl_script) + + if result: + first_user_knob = result.group(2) + # Collect user knobs from the end of the knob list + for knob in reversed(node.allKnobs()): + knob_name = knob.name() + if not knob_name: + # Ignore unnamed knob + continue + + knob_type = nuke.knob(knob.fullyQualifiedName(), type=True) + value = knob.value() + + if ( + knob_type not in EXCLUDED_KNOB_TYPE_ON_READ or + # For compating read-only string data that imprinted + # by `nuke.Text_Knob`. + (knob_type == 26 and value) + ): + key = compat_prefixed(knob_name) + data[key] = value + + if knob_name == first_user_knob: + break + + return data + + +def get_node_path(path, padding=4): + """Get filename for the Nuke write with padded number as '#' + + Arguments: + path (str): The path to render to. + + Returns: + tuple: head, padding, tail (extension) + + Examples: + >>> get_frame_path("test.exr") + ('test', 4, '.exr') + + >>> get_frame_path("filename.#####.tif") + ('filename.', 5, '.tif') + + >>> get_frame_path("foobar##.tif") + ('foobar', 2, '.tif') + + >>> get_frame_path("foobar_%08d.tif") + ('foobar_', 8, '.tif') + """ + filename, ext = os.path.splitext(path) + + # Find a final number group + if '%' in filename: + match = re.match('.*?(%[0-9]+d)$', filename) + if match: + padding = int(match.group(1).replace('%', '').replace('d', '')) + # remove number from end since fusion + # will swap it with the frame number + filename = filename.replace(match.group(1), '') + elif '#' in filename: + match = re.match('.*?(#+)$', filename) + + if match: + padding = len(match.group(1)) + # remove number from end since fusion + # will swap it with the frame number + filename = filename.replace(match.group(1), '') + + return filename, padding, ext def get_nuke_imageio_settings(): - return get_anatomy_settings(opnl.project_name)["imageio"]["nuke"] + return get_anatomy_settings(Context.project_name)["imageio"]["nuke"] def get_created_node_imageio_setting(**kwarg): @@ -103,14 +558,15 @@ def check_inventory_versions(): and check if the node is having actual version. If not then it will color it to red. """ + from .pipeline import parse_container + # get all Loader nodes by avalon attribute metadata for each in nuke.allNodes(): - container = avalon.nuke.parse_container(each) + container = parse_container(each) if container: node = nuke.toNode(container["objectName"]) - avalon_knob_data = avalon.nuke.read( - node) + avalon_knob_data = read(node) # get representation from io representation = io.find_one({ @@ -163,11 +619,10 @@ def writes_version_sync(): for each in nuke.allNodes(filter="Write"): # check if the node is avalon tracked - if opnl._node_tab_name not in each.knobs(): + if _NODE_TAB_NAME not in each.knobs(): continue - avalon_knob_data = avalon.nuke.read( - each) + avalon_knob_data = read(each) try: if avalon_knob_data['families'] not in ["render"]: @@ -209,14 +664,14 @@ def check_subsetname_exists(nodes, subset_name): bool: True of False """ return next((True for n in nodes - if subset_name in avalon.nuke.read(n).get("subset", "")), + if subset_name in read(n).get("subset", "")), False) def get_render_path(node): ''' Generate Render path from presets regarding avalon knob data ''' - data = {'avalon': avalon.nuke.read(node)} + data = {'avalon': read(node)} data_preset = { "nodeclass": data['avalon']['family'], "families": [data['avalon']['families']], @@ -385,7 +840,7 @@ def create_write_node(name, data, input=None, prenodes=None, for knob in imageio_writes["knobs"]: _data.update({knob["name"]: knob["value"]}) - _data = anlib.fix_data_for_node_create(_data) + _data = fix_data_for_node_create(_data) log.debug("_data: `{}`".format(_data)) @@ -466,7 +921,7 @@ def create_write_node(name, data, input=None, prenodes=None, prev_node = now_node # creating write node - write_node = now_node = anlib.add_write_node( + write_node = now_node = add_write_node( "inside_{}".format(name), **_data ) @@ -484,8 +939,8 @@ def create_write_node(name, data, input=None, prenodes=None, now_node.setInput(0, prev_node) # imprinting group node - anlib.set_avalon_knob_data(GN, data["avalon"]) - anlib.add_publish_knob(GN) + set_avalon_knob_data(GN, data["avalon"]) + add_publish_knob(GN) add_rendering_knobs(GN, farm) if review: @@ -537,7 +992,7 @@ def create_write_node(name, data, input=None, prenodes=None, add_deadline_tab(GN) # open the our Tab as default - GN[opnl._node_tab_name].setFlag(0) + GN[_NODE_TAB_NAME].setFlag(0) # set tile color tile_color = _data.get("tile_color", "0xff0000ff") @@ -663,7 +1118,7 @@ class WorkfileSettings(object): root_node=None, nodes=None, **kwargs): - opnl._project = kwargs.get( + Context._project_doc = kwargs.get( "project") or io.find_one({"type": "project"}) self._asset = kwargs.get("asset_name") or api.Session["AVALON_ASSET"] self._asset_entity = get_asset(self._asset) @@ -804,8 +1259,6 @@ class WorkfileSettings(object): ''' Adds correct colorspace to write node dict ''' - from avalon.nuke import read - for node in nuke.allNodes(filter="Group"): # get data from avalon knob @@ -1005,7 +1458,7 @@ class WorkfileSettings(object): node['frame_range_lock'].setValue(True) # adding handle_start/end to root avalon knob - if not anlib.set_avalon_knob_data(self._root_node, { + if not set_avalon_knob_data(self._root_node, { "handleStart": int(handle_start), "handleEnd": int(handle_end) }): @@ -1089,6 +1542,8 @@ class WorkfileSettings(object): self.set_colorspace() def set_favorites(self): + from .utils import set_context_favorites + work_dir = os.getenv("AVALON_WORKDIR") asset = os.getenv("AVALON_ASSET") favorite_items = OrderedDict() @@ -1096,9 +1551,9 @@ class WorkfileSettings(object): # project # get project's root and split to parts projects_root = os.path.normpath(work_dir.split( - opnl.project_name)[0]) + Context.project_name)[0]) # add project name - project_dir = os.path.join(projects_root, opnl.project_name) + "/" + project_dir = os.path.join(projects_root, Context.project_name) + "/" # add to favorites favorite_items.update({"Project dir": project_dir.replace("\\", "/")}) @@ -1145,8 +1600,7 @@ def get_write_node_template_attr(node): ''' # get avalon data from node data = dict() - data['avalon'] = avalon.nuke.read( - node) + data['avalon'] = read(node) data_preset = { "nodeclass": data['avalon']['family'], "families": [data['avalon']['families']], @@ -1167,7 +1621,7 @@ def get_write_node_template_attr(node): if k not in ["_id", "_previous"]} # fix badly encoded data - return anlib.fix_data_for_node_create(correct_data) + return fix_data_for_node_create(correct_data) def get_dependent_nodes(nodes): @@ -1274,13 +1728,53 @@ def find_free_space_to_paste_nodes( return xpos, ypos +@contextlib.contextmanager +def maintained_selection(): + """Maintain selection during context + + Example: + >>> with maintained_selection(): + ... node['selected'].setValue(True) + >>> print(node['selected'].value()) + False + """ + previous_selection = nuke.selectedNodes() + try: + yield + finally: + # unselect all selection in case there is some + current_seletion = nuke.selectedNodes() + [n['selected'].setValue(False) for n in current_seletion] + # and select all previously selected nodes + if previous_selection: + [n['selected'].setValue(True) for n in previous_selection] + + +def reset_selection(): + """Deselect all selected nodes""" + for node in nuke.selectedNodes(): + node["selected"].setValue(False) + + +def select_nodes(nodes): + """Selects all inputed nodes + + Arguments: + nodes (list): nuke nodes to be selected + """ + assert isinstance(nodes, (list, tuple)), "nodes has to be list or tuple" + + for node in nodes: + node["selected"].setValue(True) + + def launch_workfiles_app(): '''Function letting start workfiles after start of host ''' from openpype.lib import ( env_value_to_bool ) - from avalon.nuke.pipeline import get_main_window + from .pipeline import get_main_window # get all imortant settings open_at_start = env_value_to_bool( @@ -1291,8 +1785,8 @@ def launch_workfiles_app(): if not open_at_start: return - if not opnl.workfiles_launched: - opnl.workfiles_launched = True + if not Context.workfiles_launched: + Context.workfiles_launched = True main_window = get_main_window() host_tools.show_workfiles(parent=main_window) @@ -1378,7 +1872,7 @@ def recreate_instance(origin_node, avalon_data=None): knobs_wl = ["render", "publish", "review", "ypos", "use_limit", "first", "last"] # get data from avalon knobs - data = anlib.get_avalon_knob_data( + data = get_avalon_knob_data( origin_node) # add input data to avalon data @@ -1494,3 +1988,45 @@ def dirmap_file_name_filter(file_name): if os.path.exists(dirmap_processor.file_name): return dirmap_processor.file_name return file_name + + +# ------------------------------------ +# This function seems to be deprecated +# ------------------------------------ +def ls_img_sequence(path): + """Listing all available coherent image sequence from path + + Arguments: + path (str): A nuke's node object + + Returns: + data (dict): with nuke formated path and frameranges + """ + file = os.path.basename(path) + dirpath = os.path.dirname(path) + base, ext = os.path.splitext(file) + name, padding = os.path.splitext(base) + + # populate list of files + files = [ + f for f in os.listdir(dirpath) + if name in f + if ext in f + ] + + # create collection from list of files + collections, reminder = clique.assemble(files) + + if len(collections) > 0: + head = collections[0].format("{head}") + padding = collections[0].format("{padding}") % 1 + padding = "#" * len(padding) + tail = collections[0].format("{tail}") + file = head + padding + tail + + return { + "path": os.path.join(dirpath, file).replace("\\", "/"), + "frames": collections[0].format("[{ranges}]") + } + + return False diff --git a/openpype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py deleted file mode 100644 index 86293edb99..0000000000 --- a/openpype/hosts/nuke/api/menu.py +++ /dev/null @@ -1,166 +0,0 @@ -import os -import nuke -from avalon.nuke.pipeline import get_main_window - -from .lib import WorkfileSettings -from openpype.api import Logger, BuildWorkfile, get_current_project_settings -from openpype.tools.utils import host_tools - - -log = Logger().get_logger(__name__) - -menu_label = os.environ["AVALON_LABEL"] -context_label = None - - -def change_context_label(*args): - global context_label - menubar = nuke.menu("Nuke") - menu = menubar.findItem(menu_label) - - label = "{0}, {1}".format( - os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] - ) - - rm_item = [ - (i, item) for i, item in enumerate(menu.items()) - if context_label in item.name() - ][0] - - menu.removeItem(rm_item[1].name()) - - context_action = menu.addCommand( - label, - index=(rm_item[0]) - ) - context_action.setEnabled(False) - - log.info("Task label changed from `{}` to `{}`".format( - context_label, label)) - - context_label = label - - - -def install(): - from openpype.hosts.nuke.api import reload_config - - global context_label - - # uninstall original avalon menu - uninstall() - - main_window = get_main_window() - menubar = nuke.menu("Nuke") - menu = menubar.addMenu(menu_label) - - label = "{0}, {1}".format( - os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] - ) - context_label = label - context_action = menu.addCommand(label) - context_action.setEnabled(False) - - menu.addSeparator() - menu.addCommand( - "Work Files...", - lambda: host_tools.show_workfiles(parent=main_window) - ) - - menu.addSeparator() - menu.addCommand( - "Create...", - lambda: host_tools.show_creator(parent=main_window) - ) - menu.addCommand( - "Load...", - lambda: host_tools.show_loader( - parent=main_window, - use_context=True - ) - ) - menu.addCommand( - "Publish...", - lambda: host_tools.show_publish(parent=main_window) - ) - menu.addCommand( - "Manage...", - lambda: host_tools.show_scene_inventory(parent=main_window) - ) - - menu.addSeparator() - menu.addCommand( - "Set Resolution", - lambda: WorkfileSettings().reset_resolution() - ) - menu.addCommand( - "Set Frame Range", - lambda: WorkfileSettings().reset_frame_range_handles() - ) - menu.addCommand( - "Set Colorspace", - lambda: WorkfileSettings().set_colorspace() - ) - menu.addCommand( - "Apply All Settings", - lambda: WorkfileSettings().set_context_settings() - ) - - menu.addSeparator() - menu.addCommand( - "Build Workfile", - lambda: BuildWorkfile().process() - ) - - menu.addSeparator() - menu.addCommand( - "Experimental tools...", - lambda: host_tools.show_experimental_tools_dialog(parent=main_window) - ) - - # add reload pipeline only in debug mode - if bool(os.getenv("NUKE_DEBUG")): - menu.addSeparator() - menu.addCommand("Reload Pipeline", reload_config) - - # adding shortcuts - add_shortcuts_from_presets() - - -def uninstall(): - - menubar = nuke.menu("Nuke") - menu = menubar.findItem(menu_label) - - for item in menu.items(): - log.info("Removing menu item: {}".format(item.name())) - menu.removeItem(item.name()) - - -def add_shortcuts_from_presets(): - menubar = nuke.menu("Nuke") - nuke_presets = get_current_project_settings()["nuke"]["general"] - - if nuke_presets.get("menu"): - menu_label_mapping = { - "manage": "Manage...", - "create": "Create...", - "load": "Load...", - "build_workfile": "Build Workfile", - "publish": "Publish..." - } - - for command_name, shortcut_str in nuke_presets.get("menu").items(): - log.info("menu_name `{}` | menu_label `{}`".format( - command_name, menu_label - )) - log.info("Adding Shortcut `{}` to `{}`".format( - shortcut_str, command_name - )) - try: - menu = menubar.findItem(menu_label) - item_label = menu_label_mapping[command_name] - menuitem = menu.findItem(item_label) - menuitem.setShortcut(shortcut_str) - except AttributeError as e: - log.error(e) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py new file mode 100644 index 0000000000..c47187666b --- /dev/null +++ b/openpype/hosts/nuke/api/pipeline.py @@ -0,0 +1,421 @@ +import os +import importlib +from collections import OrderedDict + +import nuke + +import pyblish.api +import avalon.api +from avalon import pipeline + +import openpype +from openpype.api import ( + Logger, + BuildWorkfile, + get_current_project_settings +) +from openpype.tools.utils import host_tools + +from .command import viewer_update_and_undo_stop +from .lib import ( + add_publish_knob, + WorkfileSettings, + process_workfile_builder, + launch_workfiles_app, + check_inventory_versions, + set_avalon_knob_data, + read, + Context +) + +log = Logger.get_logger(__name__) + +AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.nuke.__file__)) +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + +MENU_LABEL = os.environ["AVALON_LABEL"] + + +# registering pyblish gui regarding settings in presets +if os.getenv("PYBLISH_GUI", None): + pyblish.api.register_gui(os.getenv("PYBLISH_GUI", None)) + + +def get_main_window(): + """Acquire Nuke's main window""" + if Context.main_window is None: + from Qt import QtWidgets + + top_widgets = QtWidgets.QApplication.topLevelWidgets() + name = "Foundry::UI::DockMainWindow" + for widget in top_widgets: + if ( + widget.inherits("QMainWindow") + and widget.metaObject().className() == name + ): + Context.main_window = widget + break + return Context.main_window + + +def reload_config(): + """Attempt to reload pipeline at run-time. + + CAUTION: This is primarily for development and debugging purposes. + + """ + + for module in ( + "{}.api".format(AVALON_CONFIG), + "{}.hosts.nuke.api.actions".format(AVALON_CONFIG), + "{}.hosts.nuke.api.menu".format(AVALON_CONFIG), + "{}.hosts.nuke.api.plugin".format(AVALON_CONFIG), + "{}.hosts.nuke.api.lib".format(AVALON_CONFIG), + ): + log.info("Reloading module: {}...".format(module)) + + module = importlib.import_module(module) + + try: + importlib.reload(module) + except AttributeError as e: + from importlib import reload + log.warning("Cannot reload module: {}".format(e)) + reload(module) + + +def install(): + ''' Installing all requarements for Nuke host + ''' + + pyblish.api.register_host("nuke") + + log.info("Registering Nuke plug-ins..") + pyblish.api.register_plugin_path(PUBLISH_PATH) + avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH) + avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) + + # Register Avalon event for workfiles loading. + avalon.api.on("workio.open_file", check_inventory_versions) + avalon.api.on("taskChanged", change_context_label) + + pyblish.api.register_callback( + "instanceToggled", on_pyblish_instance_toggled) + workfile_settings = WorkfileSettings() + # Disable all families except for the ones we explicitly want to see + family_states = [ + "write", + "review", + "nukenodes", + "model", + "gizmo" + ] + + avalon.api.data["familiesStateDefault"] = False + avalon.api.data["familiesStateToggled"] = family_states + + # Set context settings. + nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root") + nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root") + nuke.addOnCreate(process_workfile_builder, nodeClass="Root") + nuke.addOnCreate(launch_workfiles_app, nodeClass="Root") + _install_menu() + + +def uninstall(): + '''Uninstalling host's integration + ''' + log.info("Deregistering Nuke plug-ins..") + pyblish.deregister_host("nuke") + pyblish.api.deregister_plugin_path(PUBLISH_PATH) + avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH) + + pyblish.api.deregister_callback( + "instanceToggled", on_pyblish_instance_toggled) + + reload_config() + _uninstall_menu() + + +def _install_menu(): + # uninstall original avalon menu + main_window = get_main_window() + menubar = nuke.menu("Nuke") + menu = menubar.addMenu(MENU_LABEL) + + label = "{0}, {1}".format( + os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] + ) + Context.context_label = label + context_action = menu.addCommand(label) + context_action.setEnabled(False) + + menu.addSeparator() + menu.addCommand( + "Work Files...", + lambda: host_tools.show_workfiles(parent=main_window) + ) + + menu.addSeparator() + menu.addCommand( + "Create...", + lambda: host_tools.show_creator(parent=main_window) + ) + menu.addCommand( + "Load...", + lambda: host_tools.show_loader( + parent=main_window, + use_context=True + ) + ) + menu.addCommand( + "Publish...", + lambda: host_tools.show_publish(parent=main_window) + ) + menu.addCommand( + "Manage...", + lambda: host_tools.show_scene_inventory(parent=main_window) + ) + + menu.addSeparator() + menu.addCommand( + "Set Resolution", + lambda: WorkfileSettings().reset_resolution() + ) + menu.addCommand( + "Set Frame Range", + lambda: WorkfileSettings().reset_frame_range_handles() + ) + menu.addCommand( + "Set Colorspace", + lambda: WorkfileSettings().set_colorspace() + ) + menu.addCommand( + "Apply All Settings", + lambda: WorkfileSettings().set_context_settings() + ) + + menu.addSeparator() + menu.addCommand( + "Build Workfile", + lambda: BuildWorkfile().process() + ) + + menu.addSeparator() + menu.addCommand( + "Experimental tools...", + lambda: host_tools.show_experimental_tools_dialog(parent=main_window) + ) + + # add reload pipeline only in debug mode + if bool(os.getenv("NUKE_DEBUG")): + menu.addSeparator() + menu.addCommand("Reload Pipeline", reload_config) + + # adding shortcuts + add_shortcuts_from_presets() + + +def _uninstall_menu(): + menubar = nuke.menu("Nuke") + menu = menubar.findItem(MENU_LABEL) + + for item in menu.items(): + log.info("Removing menu item: {}".format(item.name())) + menu.removeItem(item.name()) + + +def change_context_label(*args): + menubar = nuke.menu("Nuke") + menu = menubar.findItem(MENU_LABEL) + + label = "{0}, {1}".format( + os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] + ) + + rm_item = [ + (i, item) for i, item in enumerate(menu.items()) + if Context.context_label in item.name() + ][0] + + menu.removeItem(rm_item[1].name()) + + context_action = menu.addCommand( + label, + index=(rm_item[0]) + ) + context_action.setEnabled(False) + + log.info("Task label changed from `{}` to `{}`".format( + Context.context_label, label)) + + +def add_shortcuts_from_presets(): + menubar = nuke.menu("Nuke") + nuke_presets = get_current_project_settings()["nuke"]["general"] + + if nuke_presets.get("menu"): + menu_label_mapping = { + "manage": "Manage...", + "create": "Create...", + "load": "Load...", + "build_workfile": "Build Workfile", + "publish": "Publish..." + } + + for command_name, shortcut_str in nuke_presets.get("menu").items(): + log.info("menu_name `{}` | menu_label `{}`".format( + command_name, MENU_LABEL + )) + log.info("Adding Shortcut `{}` to `{}`".format( + shortcut_str, command_name + )) + try: + menu = menubar.findItem(MENU_LABEL) + item_label = menu_label_mapping[command_name] + menuitem = menu.findItem(item_label) + menuitem.setShortcut(shortcut_str) + except AttributeError as e: + log.error(e) + + +def on_pyblish_instance_toggled(instance, old_value, new_value): + """Toggle node passthrough states on instance toggles.""" + + log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( + instance, old_value, new_value)) + + # Whether instances should be passthrough based on new value + + with viewer_update_and_undo_stop(): + n = instance[0] + try: + n["publish"].value() + except ValueError: + n = add_publish_knob(n) + log.info(" `Publish` knob was added to write node..") + + n["publish"].setValue(new_value) + + +def containerise(node, + name, + namespace, + context, + loader=None, + data=None): + """Bundle `node` into an assembly and imprint it with metadata + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + node (nuke.Node): Nuke's node object to imprint as container + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + context (dict): Asset information + loader (str, optional): Name of node used to produce this container. + + Returns: + node (nuke.Node): containerised nuke's node object + + """ + data = OrderedDict( + [ + ("schema", "openpype:container-2.0"), + ("id", pipeline.AVALON_CONTAINER_ID), + ("name", name), + ("namespace", namespace), + ("loader", str(loader)), + ("representation", context["representation"]["_id"]), + ], + + **data or dict() + ) + + set_avalon_knob_data(node, data) + + return node + + +def parse_container(node): + """Returns containerised data of a node + + Reads the imprinted data from `containerise`. + + Arguments: + node (nuke.Node): Nuke's node object to read imprinted data + + Returns: + dict: The container schema data for this container node. + + """ + data = read(node) + + # (TODO) Remove key validation when `ls` has re-implemented. + # + # If not all required data return the empty container + required = ["schema", "id", "name", + "namespace", "loader", "representation"] + if not all(key in data for key in required): + return + + # Store the node's name + data["objectName"] = node["name"].value() + + return data + + +def update_container(node, keys=None): + """Returns node with updateted containder data + + Arguments: + node (nuke.Node): The node in Nuke to imprint as container, + keys (dict, optional): data which should be updated + + Returns: + node (nuke.Node): nuke node with updated container data + + Raises: + TypeError on given an invalid container node + + """ + keys = keys or dict() + + container = parse_container(node) + if not container: + raise TypeError("Not a valid container node.") + + container.update(keys) + node = set_avalon_knob_data(node, container) + + return node + + +def ls(): + """List available containers. + + This function is used by the Container Manager in Nuke. You'll + need to implement a for-loop that then *yields* one Container at + a time. + + See the `container.json` schema for details on how it should look, + and the Maya equivalent, which is in `avalon.maya.pipeline` + """ + all_nodes = nuke.allNodes(recurseGroups=False) + + # TODO: add readgeo, readcamera, readimage + nodes = [n for n in all_nodes] + + for n in nodes: + log.debug("name: `{}`".format(n.name())) + container = parse_container(n) + if container: + yield container diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 82299dd354..66b42f7bb1 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -2,23 +2,30 @@ import os import random import string -import avalon.nuke -from avalon.nuke import lib as anlib -from avalon import api +import nuke + +import avalon.api from openpype.api import ( get_current_project_settings, PypeCreatorMixin ) -from .lib import check_subsetname_exists -import nuke +from .lib import ( + Knobby, + check_subsetname_exists, + reset_selection, + maintained_selection, + set_avalon_knob_data, + add_publish_knob +) -class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator): - """Pype Nuke Creator class wrapper - """ +class OpenPypeCreator(PypeCreatorMixin, avalon.api.Creator): + """Pype Nuke Creator class wrapper""" + node_color = "0xdfea5dff" + def __init__(self, *args, **kwargs): - super(PypeCreator, self).__init__(*args, **kwargs) + super(OpenPypeCreator, self).__init__(*args, **kwargs) self.presets = get_current_project_settings()["nuke"]["create"].get( self.__class__.__name__, {} ) @@ -31,6 +38,38 @@ class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator): raise NameError("`{0}: {1}".format(__name__, msg)) return + def process(self): + from nukescripts import autoBackdrop + + instance = None + + if (self.options or {}).get("useSelection"): + + nodes = nuke.selectedNodes() + if not nodes: + nuke.message("Please select nodes that you " + "wish to add to a container") + return + + elif len(nodes) == 1: + # only one node is selected + instance = nodes[0] + + if not instance: + # Not using selection or multiple nodes selected + bckd_node = autoBackdrop() + bckd_node["tile_color"].setValue(int(self.node_color, 16)) + bckd_node["note_font_size"].setValue(24) + bckd_node["label"].setValue("[{}]".format(self.name)) + + instance = bckd_node + + # add avalon knobs + set_avalon_knob_data(instance, self.data) + add_publish_knob(instance) + + return instance + def get_review_presets_config(): settings = get_current_project_settings() @@ -48,7 +87,7 @@ def get_review_presets_config(): return [str(name) for name, _prop in outputs.items()] -class NukeLoader(api.Loader): +class NukeLoader(avalon.api.Loader): container_id_knob = "containerId" container_id = None @@ -74,7 +113,7 @@ class NukeLoader(api.Loader): node[self.container_id_knob].setValue(source_id) else: HIDEN_FLAG = 0x00040000 - _knob = anlib.Knobby( + _knob = Knobby( "String_Knob", self.container_id, flags=[ @@ -183,7 +222,7 @@ class ExporterReview(object): Returns: nuke.Node: copy node of Input Process node """ - anlib.reset_selection() + reset_selection() ipn_orig = None for v in nuke.allNodes(filter="Viewer"): ip = v["input_process"].getValue() @@ -196,7 +235,7 @@ class ExporterReview(object): # copy selected to clipboard nuke.nodeCopy("%clipboard%") # reset selection - anlib.reset_selection() + reset_selection() # paste node and selection is on it only nuke.nodePaste("%clipboard%") # assign to variable @@ -396,7 +435,7 @@ class ExporterReviewMov(ExporterReview): def save_file(self): import shutil - with anlib.maintained_selection(): + with maintained_selection(): self.log.info("Saving nodes as file... ") # create nk path path = os.path.splitext(self.path)[0] + ".nk" diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index e43c11a380..f8f248357b 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -1,7 +1,8 @@ import os import nuke -from avalon.nuke import lib as anlib + from openpype.api import resources +from .lib import maintained_selection def set_context_favorites(favorites=None): @@ -55,7 +56,7 @@ def bake_gizmos_recursively(in_group=nuke.Root()): is_group (nuke.Node)[optonal]: group node or all nodes """ # preserve selection after all is done - with anlib.maintained_selection(): + with maintained_selection(): # jump to the group with in_group: for node in nuke.allNodes(): diff --git a/openpype/hosts/nuke/api/workio.py b/openpype/hosts/nuke/api/workio.py new file mode 100644 index 0000000000..dbc24fdc9b --- /dev/null +++ b/openpype/hosts/nuke/api/workio.py @@ -0,0 +1,55 @@ +"""Host API required Work Files tool""" +import os +import nuke +import avalon.api + + +def file_extensions(): + return avalon.api.HOST_WORKFILE_EXTENSIONS["nuke"] + + +def has_unsaved_changes(): + return nuke.root().modified() + + +def save_file(filepath): + path = filepath.replace("\\", "/") + nuke.scriptSaveAs(path) + nuke.Root()["name"].setValue(path) + nuke.Root()["project_directory"].setValue(os.path.dirname(path)) + nuke.Root().setModified(False) + + +def open_file(filepath): + filepath = filepath.replace("\\", "/") + + # To remain in the same window, we have to clear the script and read + # in the contents of the workfile. + nuke.scriptClear() + nuke.scriptReadFile(filepath) + nuke.Root()["name"].setValue(filepath) + nuke.Root()["project_directory"].setValue(os.path.dirname(filepath)) + nuke.Root().setModified(False) + return True + + +def current_file(): + current_file = nuke.root().name() + + # Unsaved current file + if current_file == 'Root': + return None + + return os.path.normpath(current_file).replace("\\", "/") + + +def work_root(session): + + work_dir = session["AVALON_WORKDIR"] + scene_dir = session.get("AVALON_SCENEDIR") + if scene_dir: + path = os.path.join(work_dir, scene_dir) + else: + path = work_dir + + return os.path.normpath(path).replace("\\", "/") diff --git a/openpype/hosts/nuke/plugins/create/create_backdrop.py b/openpype/hosts/nuke/plugins/create/create_backdrop.py index cda2629587..0c11b3f274 100644 --- a/openpype/hosts/nuke/plugins/create/create_backdrop.py +++ b/openpype/hosts/nuke/plugins/create/create_backdrop.py @@ -1,9 +1,12 @@ -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import plugin import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import ( + select_nodes, + set_avalon_knob_data +) -class CreateBackdrop(plugin.PypeCreator): +class CreateBackdrop(plugin.OpenPypeCreator): """Add Publishable Backdrop""" name = "nukenodes" @@ -25,14 +28,14 @@ class CreateBackdrop(plugin.PypeCreator): nodes = self.nodes if len(nodes) >= 1: - anlib.select_nodes(nodes) + select_nodes(nodes) bckd_node = autoBackdrop() bckd_node["name"].setValue("{}_BDN".format(self.name)) bckd_node["tile_color"].setValue(int(self.node_color, 16)) bckd_node["note_font_size"].setValue(24) bckd_node["label"].setValue("[{}]".format(self.name)) # add avalon knobs - instance = anlib.set_avalon_knob_data(bckd_node, self.data) + instance = set_avalon_knob_data(bckd_node, self.data) return instance else: @@ -48,6 +51,6 @@ class CreateBackdrop(plugin.PypeCreator): bckd_node["note_font_size"].setValue(24) bckd_node["label"].setValue("[{}]".format(self.name)) # add avalon knobs - instance = anlib.set_avalon_knob_data(bckd_node, self.data) + instance = set_avalon_knob_data(bckd_node, self.data) return instance diff --git a/openpype/hosts/nuke/plugins/create/create_camera.py b/openpype/hosts/nuke/plugins/create/create_camera.py index 359086d48f..3b13c80dc4 100644 --- a/openpype/hosts/nuke/plugins/create/create_camera.py +++ b/openpype/hosts/nuke/plugins/create/create_camera.py @@ -1,9 +1,11 @@ -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import plugin import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import ( + set_avalon_knob_data +) -class CreateCamera(plugin.PypeCreator): +class CreateCamera(plugin.OpenPypeCreator): """Add Publishable Backdrop""" name = "camera" @@ -36,7 +38,7 @@ class CreateCamera(plugin.PypeCreator): # change node color n["tile_color"].setValue(int(self.node_color, 16)) # add avalon knobs - anlib.set_avalon_knob_data(n, data) + set_avalon_knob_data(n, data) return True else: msg = str("Please select nodes you " @@ -49,5 +51,5 @@ class CreateCamera(plugin.PypeCreator): camera_node = nuke.createNode("Camera2") camera_node["tile_color"].setValue(int(self.node_color, 16)) # add avalon knobs - instance = anlib.set_avalon_knob_data(camera_node, self.data) + instance = set_avalon_knob_data(camera_node, self.data) return instance diff --git a/openpype/hosts/nuke/plugins/create/create_gizmo.py b/openpype/hosts/nuke/plugins/create/create_gizmo.py index c59713cff1..de73623a1e 100644 --- a/openpype/hosts/nuke/plugins/create/create_gizmo.py +++ b/openpype/hosts/nuke/plugins/create/create_gizmo.py @@ -1,9 +1,14 @@ -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import plugin import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import ( + maintained_selection, + select_nodes, + set_avalon_knob_data +) -class CreateGizmo(plugin.PypeCreator): + +class CreateGizmo(plugin.OpenPypeCreator): """Add Publishable "gizmo" group The name is symbolically gizmo as presumably @@ -28,13 +33,13 @@ class CreateGizmo(plugin.PypeCreator): nodes = self.nodes self.log.info(len(nodes)) if len(nodes) == 1: - anlib.select_nodes(nodes) + select_nodes(nodes) node = nodes[-1] # check if Group node if node.Class() in "Group": node["name"].setValue("{}_GZM".format(self.name)) node["tile_color"].setValue(int(self.node_color, 16)) - return anlib.set_avalon_knob_data(node, self.data) + return set_avalon_knob_data(node, self.data) else: msg = ("Please select a group node " "you wish to publish as the gizmo") @@ -42,7 +47,7 @@ class CreateGizmo(plugin.PypeCreator): nuke.message(msg) if len(nodes) >= 2: - anlib.select_nodes(nodes) + select_nodes(nodes) nuke.makeGroup() gizmo_node = nuke.selectedNode() gizmo_node["name"].setValue("{}_GZM".format(self.name)) @@ -57,16 +62,15 @@ class CreateGizmo(plugin.PypeCreator): "- create User knobs on the group") # add avalon knobs - return anlib.set_avalon_knob_data(gizmo_node, self.data) + return set_avalon_knob_data(gizmo_node, self.data) else: - msg = ("Please select nodes you " - "wish to add to the gizmo") + msg = "Please select nodes you wish to add to the gizmo" self.log.error(msg) nuke.message(msg) return else: - with anlib.maintained_selection(): + with maintained_selection(): gizmo_node = nuke.createNode("Group") gizmo_node["name"].setValue("{}_GZM".format(self.name)) gizmo_node["tile_color"].setValue(int(self.node_color, 16)) @@ -80,4 +84,4 @@ class CreateGizmo(plugin.PypeCreator): "- create User knobs on the group") # add avalon knobs - return anlib.set_avalon_knob_data(gizmo_node, self.data) + return set_avalon_knob_data(gizmo_node, self.data) diff --git a/openpype/hosts/nuke/plugins/create/create_model.py b/openpype/hosts/nuke/plugins/create/create_model.py index 4e30860e05..15a4e3ab8a 100644 --- a/openpype/hosts/nuke/plugins/create/create_model.py +++ b/openpype/hosts/nuke/plugins/create/create_model.py @@ -1,9 +1,11 @@ -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import plugin import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import ( + set_avalon_knob_data +) -class CreateModel(plugin.PypeCreator): +class CreateModel(plugin.OpenPypeCreator): """Add Publishable Model Geometry""" name = "model" @@ -68,7 +70,7 @@ class CreateModel(plugin.PypeCreator): # change node color n["tile_color"].setValue(int(self.node_color, 16)) # add avalon knobs - anlib.set_avalon_knob_data(n, data) + set_avalon_knob_data(n, data) return True else: msg = str("Please select nodes you " @@ -81,5 +83,5 @@ class CreateModel(plugin.PypeCreator): model_node = nuke.createNode("WriteGeo") model_node["tile_color"].setValue(int(self.node_color, 16)) # add avalon knobs - instance = anlib.set_avalon_knob_data(model_node, self.data) + instance = set_avalon_knob_data(model_node, self.data) return instance diff --git a/openpype/hosts/nuke/plugins/create/create_read.py b/openpype/hosts/nuke/plugins/create/create_read.py index bf5de23346..bdc67add42 100644 --- a/openpype/hosts/nuke/plugins/create/create_read.py +++ b/openpype/hosts/nuke/plugins/create/create_read.py @@ -1,13 +1,16 @@ from collections import OrderedDict -import avalon.api -import avalon.nuke -from openpype import api as pype -from openpype.hosts.nuke.api import plugin import nuke +import avalon.api +from openpype import api as pype +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import ( + set_avalon_knob_data +) -class CrateRead(plugin.PypeCreator): + +class CrateRead(plugin.OpenPypeCreator): # change this to template preset name = "ReadCopy" label = "Create Read Copy" @@ -45,7 +48,7 @@ class CrateRead(plugin.PypeCreator): continue avalon_data = self.data avalon_data['subset'] = "{}".format(self.name) - avalon.nuke.lib.set_avalon_knob_data(node, avalon_data) + set_avalon_knob_data(node, avalon_data) node['tile_color'].setValue(16744935) count_reads += 1 diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 1b925014ad..3285e5f92d 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -1,11 +1,12 @@ from collections import OrderedDict -from openpype.hosts.nuke.api import ( - plugin, - lib) + import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import create_write_node -class CreateWritePrerender(plugin.PypeCreator): + +class CreateWritePrerender(plugin.OpenPypeCreator): # change this to template preset name = "WritePrerender" label = "Create Write Prerender" @@ -98,7 +99,7 @@ class CreateWritePrerender(plugin.PypeCreator): self.log.info("write_data: {}".format(write_data)) - write_node = lib.create_write_node( + write_node = create_write_node( self.data["subset"], write_data, input=selected_node, diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 5f13fddf4e..a9c4b5341e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -1,11 +1,12 @@ from collections import OrderedDict -from openpype.hosts.nuke.api import ( - plugin, - lib) + import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import create_write_node -class CreateWriteRender(plugin.PypeCreator): + +class CreateWriteRender(plugin.OpenPypeCreator): # change this to template preset name = "WriteRender" label = "Create Write Render" @@ -119,7 +120,7 @@ class CreateWriteRender(plugin.PypeCreator): } ] - write_node = lib.create_write_node( + write_node = create_write_node( self.data["subset"], write_data, input=selected_node, diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index eebb5613c3..0037b64ce3 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -1,11 +1,12 @@ from collections import OrderedDict -from openpype.hosts.nuke.api import ( - plugin, - lib) + import nuke +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import create_write_node -class CreateWriteStill(plugin.PypeCreator): + +class CreateWriteStill(plugin.OpenPypeCreator): # change this to template preset name = "WriteStillFrame" label = "Create Write Still Image" @@ -108,7 +109,7 @@ class CreateWriteStill(plugin.PypeCreator): } ] - write_node = lib.create_write_node( + write_node = create_write_node( self.name, write_data, input=selected_node, diff --git a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py index e7ae51fa86..49405fd213 100644 --- a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py +++ b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py @@ -1,7 +1,6 @@ from avalon import api, style -from avalon.nuke import lib as anlib -from openpype.api import ( - Logger) +from openpype.api import Logger +from openpype.hosts.nuke.api.lib import set_avalon_knob_data class RepairOldLoaders(api.InventoryAction): @@ -10,7 +9,7 @@ class RepairOldLoaders(api.InventoryAction): icon = "gears" color = style.colors.alert - log = Logger().get_logger(__name__) + log = Logger.get_logger(__name__) def process(self, containers): import nuke @@ -34,4 +33,4 @@ class RepairOldLoaders(api.InventoryAction): }) node["name"].setValue(new_name) # get data from avalon knob - anlib.set_avalon_knob_data(node, cdata) + set_avalon_knob_data(node, cdata) diff --git a/openpype/hosts/nuke/plugins/inventory/select_containers.py b/openpype/hosts/nuke/plugins/inventory/select_containers.py index bd00983172..3f174b3562 100644 --- a/openpype/hosts/nuke/plugins/inventory/select_containers.py +++ b/openpype/hosts/nuke/plugins/inventory/select_containers.py @@ -1,4 +1,5 @@ from avalon import api +from openpype.hosts.nuke.api.commands import viewer_update_and_undo_stop class SelectContainers(api.InventoryAction): @@ -9,11 +10,10 @@ class SelectContainers(api.InventoryAction): def process(self, containers): import nuke - import avalon.nuke nodes = [nuke.toNode(i["objectName"]) for i in containers] - with avalon.nuke.viewer_update_and_undo_stop(): + with viewer_update_and_undo_stop(): # clear previous_selection [n['selected'].setValue(False) for n in nodes] # Select tool diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index 9148260e9e..a2bd458948 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -1,9 +1,18 @@ from avalon import api, style, io import nuke import nukescripts -from openpype.hosts.nuke.api import lib as pnlib -from avalon.nuke import lib as anlib -from avalon.nuke import containerise, update_container + +from openpype.hosts.nuke.api.lib import ( + find_free_space_to_paste_nodes, + maintained_selection, + reset_selection, + select_nodes, + get_avalon_knob_data, + set_avalon_knob_data +) +from openpype.hosts.nuke.api.commands import viewer_update_and_undo_stop +from openpype.hosts.nuke.api import containerise, update_container + class LoadBackdropNodes(api.Loader): """Loading Published Backdrop nodes (workfile, nukenodes)""" @@ -66,12 +75,12 @@ class LoadBackdropNodes(api.Loader): # Get mouse position n = nuke.createNode("NoOp") xcursor, ycursor = (n.xpos(), n.ypos()) - anlib.reset_selection() + reset_selection() nuke.delete(n) bdn_frame = 50 - with anlib.maintained_selection(): + with maintained_selection(): # add group from nk nuke.nodePaste(file) @@ -81,11 +90,13 @@ class LoadBackdropNodes(api.Loader): nodes = nuke.selectedNodes() # get pointer position in DAG - xpointer, ypointer = pnlib.find_free_space_to_paste_nodes(nodes, direction="right", offset=200+bdn_frame) + xpointer, ypointer = find_free_space_to_paste_nodes( + nodes, direction="right", offset=200 + bdn_frame + ) # reset position to all nodes and replace inputs and output for n in nodes: - anlib.reset_selection() + reset_selection() xpos = (n.xpos() - xcursor) + xpointer ypos = (n.ypos() - ycursor) + ypointer n.setXYpos(xpos, ypos) @@ -108,7 +119,7 @@ class LoadBackdropNodes(api.Loader): d.setInput(index, dot) # remove Input node - anlib.reset_selection() + reset_selection() nuke.delete(n) continue @@ -127,15 +138,15 @@ class LoadBackdropNodes(api.Loader): dot.setInput(0, dep) # remove Input node - anlib.reset_selection() + reset_selection() nuke.delete(n) continue else: new_nodes.append(n) # reselect nodes with new Dot instead of Inputs and Output - anlib.reset_selection() - anlib.select_nodes(new_nodes) + reset_selection() + select_nodes(new_nodes) # place on backdrop bdn = nukescripts.autoBackdrop() @@ -208,16 +219,16 @@ class LoadBackdropNodes(api.Loader): # just in case we are in group lets jump out of it nuke.endGroup() - with anlib.maintained_selection(): + with maintained_selection(): xpos = GN.xpos() ypos = GN.ypos() - avalon_data = anlib.get_avalon_knob_data(GN) + avalon_data = get_avalon_knob_data(GN) nuke.delete(GN) # add group from nk nuke.nodePaste(file) GN = nuke.selectedNode() - anlib.set_avalon_knob_data(GN, avalon_data) + set_avalon_knob_data(GN, avalon_data) GN.setXYpos(xpos, ypos) GN["name"].setValue(object_name) @@ -243,7 +254,6 @@ class LoadBackdropNodes(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 377d60e84b..b9d4bb358f 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -1,8 +1,15 @@ -from avalon import api, io -from avalon.nuke import lib as anlib -from avalon.nuke import containerise, update_container import nuke +from avalon import api, io +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) +from openpype.hosts.nuke.api.lib import ( + maintained_selection +) + class AlembicCameraLoader(api.Loader): """ @@ -43,7 +50,7 @@ class AlembicCameraLoader(api.Loader): # getting file path file = self.fname.replace("\\", "/") - with anlib.maintained_selection(): + with maintained_selection(): camera_node = nuke.createNode( "Camera2", "name {} file {} read_from_file True".format( @@ -122,7 +129,7 @@ class AlembicCameraLoader(api.Loader): # getting file path file = api.get_representation_path(representation).replace("\\", "/") - with anlib.maintained_selection(): + with maintained_selection(): camera_node = nuke.toNode(object_name) camera_node['selected'].setValue(True) @@ -181,7 +188,6 @@ class AlembicCameraLoader(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 9ce72c0519..712cdf213f 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -3,13 +3,13 @@ from avalon.vendor import qargparse from avalon import api, io from openpype.hosts.nuke.api.lib import ( - get_imageio_input_colorspace + get_imageio_input_colorspace, + maintained_selection ) -from avalon.nuke import ( +from openpype.hosts.nuke.api import ( containerise, update_container, - viewer_update_and_undo_stop, - maintained_selection + viewer_update_and_undo_stop ) from openpype.hosts.nuke.api import plugin @@ -280,9 +280,6 @@ class LoadClip(plugin.NukeLoader): self.set_as_member(read_node) def remove(self, container): - - from avalon.nuke import viewer_update_and_undo_stop - read_node = nuke.toNode(container['objectName']) assert read_node.Class() == "Read", "Must be Read" @@ -378,4 +375,4 @@ class LoadClip(plugin.NukeLoader): "class_name": self.__class__.__name__ } - return self.node_name_template.format(**name_data) \ No newline at end of file + return self.node_name_template.format(**name_data) diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index 8ba1b6b7c1..8b8867feba 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -1,7 +1,12 @@ -from avalon import api, style, io -import nuke import json from collections import OrderedDict +import nuke +from avalon import api, style, io +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class LoadEffects(api.Loader): @@ -30,9 +35,6 @@ class LoadEffects(api.Loader): Returns: nuke node: containerised nuke node object """ - # import dependencies - from avalon.nuke import containerise - # get main variables version = context['version'] version_data = version.get("data", {}) @@ -138,10 +140,6 @@ class LoadEffects(api.Loader): inputs: """ - - from avalon.nuke import ( - update_container - ) # get main variables # Get version from io version = io.find_one({ @@ -338,7 +336,6 @@ class LoadEffects(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index d0cab26842..7948cbba9a 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -1,8 +1,15 @@ -from avalon import api, style, io -import nuke import json from collections import OrderedDict + +import nuke + +from avalon import api, style, io from openpype.hosts.nuke.api import lib +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class LoadEffectsInputProcess(api.Loader): @@ -30,8 +37,6 @@ class LoadEffectsInputProcess(api.Loader): Returns: nuke node: containerised nuke node object """ - # import dependencies - from avalon.nuke import containerise # get main variables version = context['version'] @@ -142,9 +147,6 @@ class LoadEffectsInputProcess(api.Loader): """ - from avalon.nuke import ( - update_container - ) # get main variables # Get version from io version = io.find_one({ @@ -355,7 +357,6 @@ class LoadEffectsInputProcess(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py index c6228b95f6..f549623b88 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo.py @@ -1,7 +1,15 @@ -from avalon import api, style, io import nuke -from avalon.nuke import lib as anlib -from avalon.nuke import containerise, update_container +from avalon import api, style, io +from openpype.hosts.nuke.api.lib import ( + maintained_selection, + get_avalon_knob_data, + set_avalon_knob_data +) +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class LoadGizmo(api.Loader): @@ -61,7 +69,7 @@ class LoadGizmo(api.Loader): # just in case we are in group lets jump out of it nuke.endGroup() - with anlib.maintained_selection(): + with maintained_selection(): # add group from nk nuke.nodePaste(file) @@ -122,16 +130,16 @@ class LoadGizmo(api.Loader): # just in case we are in group lets jump out of it nuke.endGroup() - with anlib.maintained_selection(): + with maintained_selection(): xpos = GN.xpos() ypos = GN.ypos() - avalon_data = anlib.get_avalon_knob_data(GN) + avalon_data = get_avalon_knob_data(GN) nuke.delete(GN) # add group from nk nuke.nodePaste(file) GN = nuke.selectedNode() - anlib.set_avalon_knob_data(GN, avalon_data) + set_avalon_knob_data(GN, avalon_data) GN.setXYpos(xpos, ypos) GN["name"].setValue(object_name) @@ -157,7 +165,6 @@ class LoadGizmo(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index 5ca101d6cb..4f17446673 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -1,8 +1,16 @@ from avalon import api, style, io import nuke -from openpype.hosts.nuke.api import lib as pnlib -from avalon.nuke import lib as anlib -from avalon.nuke import containerise, update_container +from openpype.hosts.nuke.api.lib import ( + maintained_selection, + create_backdrop, + get_avalon_knob_data, + set_avalon_knob_data +) +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class LoadGizmoInputProcess(api.Loader): @@ -62,7 +70,7 @@ class LoadGizmoInputProcess(api.Loader): # just in case we are in group lets jump out of it nuke.endGroup() - with anlib.maintained_selection(): + with maintained_selection(): # add group from nk nuke.nodePaste(file) @@ -128,16 +136,16 @@ class LoadGizmoInputProcess(api.Loader): # just in case we are in group lets jump out of it nuke.endGroup() - with anlib.maintained_selection(): + with maintained_selection(): xpos = GN.xpos() ypos = GN.ypos() - avalon_data = anlib.get_avalon_knob_data(GN) + avalon_data = get_avalon_knob_data(GN) nuke.delete(GN) # add group from nk nuke.nodePaste(file) GN = nuke.selectedNode() - anlib.set_avalon_knob_data(GN, avalon_data) + set_avalon_knob_data(GN, avalon_data) GN.setXYpos(xpos, ypos) GN["name"].setValue(object_name) @@ -197,8 +205,12 @@ class LoadGizmoInputProcess(api.Loader): viewer["input_process_node"].setValue(group_node_name) # put backdrop under - pnlib.create_backdrop(label="Input Process", layer=2, - nodes=[viewer, group_node], color="0x7c7faaff") + create_backdrop( + label="Input Process", + layer=2, + nodes=[viewer, group_node], + color="0x7c7faaff" + ) return True @@ -234,7 +246,6 @@ class LoadGizmoInputProcess(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 02a5b55c18..427167ca98 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -7,6 +7,11 @@ from avalon import api, io from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace ) +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class LoadImage(api.Loader): @@ -46,10 +51,6 @@ class LoadImage(api.Loader): return cls.representations + cls._representations def load(self, context, name, namespace, options): - from avalon.nuke import ( - containerise, - viewer_update_and_undo_stop - ) self.log.info("__ options: `{}`".format(options)) frame_number = options.get("frame_number", 1) @@ -154,11 +155,6 @@ class LoadImage(api.Loader): inputs: """ - - from avalon.nuke import ( - update_container - ) - node = nuke.toNode(container["objectName"]) frame_number = node["first"].value() @@ -234,9 +230,6 @@ class LoadImage(api.Loader): self.log.info("udated to version: {}".format(version.get("name"))) def remove(self, container): - - from avalon.nuke import viewer_update_and_undo_stop - node = nuke.toNode(container['objectName']) assert node.Class() == "Read", "Must be Read" diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 15fa4fa35c..8c8dc7f37d 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -1,7 +1,11 @@ -from avalon import api, io -from avalon.nuke import lib as anlib -from avalon.nuke import containerise, update_container import nuke +from avalon import api, io +from openpype.hosts.nuke.api.lib import maintained_selection +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class AlembicModelLoader(api.Loader): @@ -43,7 +47,7 @@ class AlembicModelLoader(api.Loader): # getting file path file = self.fname.replace("\\", "/") - with anlib.maintained_selection(): + with maintained_selection(): model_node = nuke.createNode( "ReadGeo2", "name {} file {} ".format( @@ -122,7 +126,7 @@ class AlembicModelLoader(api.Loader): # getting file path file = api.get_representation_path(representation).replace("\\", "/") - with anlib.maintained_selection(): + with maintained_selection(): model_node = nuke.toNode(object_name) model_node['selected'].setValue(True) @@ -181,7 +185,6 @@ class AlembicModelLoader(api.Loader): self.update(container, representation) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index 7444dd6e96..8489283e8c 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -1,6 +1,11 @@ -from avalon import api, style, io -from avalon.nuke import get_avalon_knob_data import nuke +from avalon import api, style, io +from openpype.hosts.nuke.api.lib import get_avalon_knob_data +from openpype.hosts.nuke.api import ( + containerise, + update_container, + viewer_update_and_undo_stop +) class LinkAsGroup(api.Loader): @@ -15,8 +20,6 @@ class LinkAsGroup(api.Loader): color = style.colors.alert def load(self, context, name, namespace, data): - - from avalon.nuke import containerise # for k, v in context.items(): # log.info("key: `{}`, value: {}\n".format(k, v)) version = context['version'] @@ -103,11 +106,6 @@ class LinkAsGroup(api.Loader): inputs: """ - - from avalon.nuke import ( - update_container - ) - node = nuke.toNode(container['objectName']) root = api.get_representation_path(representation).replace("\\", "/") @@ -155,7 +153,6 @@ class LinkAsGroup(api.Loader): self.log.info("udated to version: {}".format(version.get("name"))) def remove(self, container): - from avalon.nuke import viewer_update_and_undo_stop node = nuke.toNode(container['objectName']) with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/openpype/hosts/nuke/plugins/publish/extract_backdrop.py b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py index 0747c15ea7..0a2df0898e 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py @@ -1,9 +1,16 @@ -import pyblish.api -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import lib as pnlib -import nuke import os + +import nuke + +import pyblish.api + import openpype +from openpype.hosts.nuke.api.lib import ( + maintained_selection, + reset_selection, + select_nodes +) + class ExtractBackdropNode(openpype.api.Extractor): """Extracting content of backdrop nodes @@ -27,7 +34,7 @@ class ExtractBackdropNode(openpype.api.Extractor): path = os.path.join(stagingdir, filename) # maintain selection - with anlib.maintained_selection(): + with maintained_selection(): # all connections outside of backdrop connections_in = instance.data["nodeConnectionsIn"] connections_out = instance.data["nodeConnectionsOut"] @@ -44,7 +51,7 @@ class ExtractBackdropNode(openpype.api.Extractor): nodes.append(inpn) tmp_nodes.append(inpn) - anlib.reset_selection() + reset_selection() # connect output node for n, output in connections_out.items(): @@ -58,11 +65,11 @@ class ExtractBackdropNode(openpype.api.Extractor): opn.autoplace() nodes.append(opn) tmp_nodes.append(opn) - anlib.reset_selection() + reset_selection() # select nodes to copy - anlib.reset_selection() - anlib.select_nodes(nodes) + reset_selection() + select_nodes(nodes) # create tmp nk file # save file to the path nuke.nodeCopy(path) diff --git a/openpype/hosts/nuke/plugins/publish/extract_camera.py b/openpype/hosts/nuke/plugins/publish/extract_camera.py index bc50dac108..942cdc537d 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_camera.py +++ b/openpype/hosts/nuke/plugins/publish/extract_camera.py @@ -1,10 +1,12 @@ -import nuke import os import math +from pprint import pformat + +import nuke + import pyblish.api import openpype.api -from avalon.nuke import lib as anlib -from pprint import pformat +from openpype.hosts.nuke.api.lib import maintained_selection class ExtractCamera(openpype.api.Extractor): @@ -52,7 +54,7 @@ class ExtractCamera(openpype.api.Extractor): filename = subset + ".{}".format(extension) file_path = os.path.join(staging_dir, filename).replace("\\", "/") - with anlib.maintained_selection(): + with maintained_selection(): # bake camera with axeses onto word coordinate XYZ rm_n = bakeCameraWithAxeses( nuke.toNode(instance.data["name"]), output_range) diff --git a/openpype/hosts/nuke/plugins/publish/extract_gizmo.py b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py index 78bf9c998d..2d5bfdeb5e 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_gizmo.py +++ b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py @@ -1,9 +1,15 @@ -import pyblish.api -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import utils as pnutils -import nuke import os +import nuke + +import pyblish.api + import openpype +from openpype.hosts.nuke.api import utils as pnutils +from openpype.hosts.nuke.api.lib import ( + maintained_selection, + reset_selection, + select_nodes +) class ExtractGizmo(openpype.api.Extractor): @@ -26,17 +32,17 @@ class ExtractGizmo(openpype.api.Extractor): path = os.path.join(stagingdir, filename) # maintain selection - with anlib.maintained_selection(): + with maintained_selection(): orig_grpn_name = orig_grpn.name() tmp_grpn_name = orig_grpn_name + "_tmp" # select original group node - anlib.select_nodes([orig_grpn]) + select_nodes([orig_grpn]) # copy to clipboard nuke.nodeCopy("%clipboard%") # reset selection to none - anlib.reset_selection() + reset_selection() # paste clipboard nuke.nodePaste("%clipboard%") diff --git a/openpype/hosts/nuke/plugins/publish/extract_model.py b/openpype/hosts/nuke/plugins/publish/extract_model.py index 43214bf3e9..0375263338 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_model.py +++ b/openpype/hosts/nuke/plugins/publish/extract_model.py @@ -1,9 +1,12 @@ -import nuke import os +from pprint import pformat +import nuke import pyblish.api import openpype.api -from avalon.nuke import lib as anlib -from pprint import pformat +from openpype.hosts.nuke.api.lib import ( + maintained_selection, + select_nodes +) class ExtractModel(openpype.api.Extractor): @@ -49,9 +52,9 @@ class ExtractModel(openpype.api.Extractor): filename = subset + ".{}".format(extension) file_path = os.path.join(staging_dir, filename).replace("\\", "/") - with anlib.maintained_selection(): + with maintained_selection(): # select model node - anlib.select_nodes([model_node]) + select_nodes([model_node]) # create write geo node wg_n = nuke.createNode("WriteGeo") diff --git a/openpype/hosts/nuke/plugins/publish/extract_ouput_node.py b/openpype/hosts/nuke/plugins/publish/extract_ouput_node.py index c3a6a3b167..e38927c3a7 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_ouput_node.py +++ b/openpype/hosts/nuke/plugins/publish/extract_ouput_node.py @@ -1,6 +1,6 @@ import nuke import pyblish.api -from avalon.nuke import maintained_selection +from openpype.hosts.nuke.api.lib import maintained_selection class CreateOutputNode(pyblish.api.ContextPlugin): diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py index 8ba746a3c4..4cf2fd7d9f 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py @@ -1,8 +1,8 @@ import os import pyblish.api -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import plugin import openpype +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import maintained_selection class ExtractReviewDataLut(openpype.api.Extractor): @@ -37,7 +37,7 @@ class ExtractReviewDataLut(openpype.api.Extractor): "StagingDir `{0}`...".format(instance.data["stagingDir"])) # generate data - with anlib.maintained_selection(): + with maintained_selection(): exporter = plugin.ExporterReviewLut( self, instance ) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 32962b57a6..13d23ffb9c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -1,8 +1,8 @@ import os import pyblish.api -from avalon.nuke import lib as anlib -from openpype.hosts.nuke.api import plugin import openpype +from openpype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api.lib import maintained_selection class ExtractReviewDataMov(openpype.api.Extractor): @@ -41,7 +41,7 @@ class ExtractReviewDataMov(openpype.api.Extractor): self.log.info(self.outputs) # generate data - with anlib.maintained_selection(): + with maintained_selection(): generated_repres = [] for o_name, o_data in self.outputs.items(): f_families = o_data["filter"]["families"] diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 0f68680742..50e5f995f4 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -1,8 +1,8 @@ import os import nuke -from avalon.nuke import lib as anlib import pyblish.api import openpype +from openpype.hosts.nuke.api.lib import maintained_selection class ExtractSlateFrame(openpype.api.Extractor): @@ -25,7 +25,7 @@ class ExtractSlateFrame(openpype.api.Extractor): else: self.viewer_lut_raw = False - with anlib.maintained_selection(): + with maintained_selection(): self.log.debug("instance: {}".format(instance)) self.log.debug("instance.data[families]: {}".format( instance.data["families"])) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 0c9af66435..ef6d486ca2 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -1,9 +1,9 @@ import sys import os import nuke -from avalon.nuke import lib as anlib import pyblish.api import openpype +from openpype.hosts.nuke.api.lib import maintained_selection if sys.version_info[0] >= 3: @@ -30,7 +30,7 @@ class ExtractThumbnail(openpype.api.Extractor): if "render.farm" in instance.data["families"]: return - with anlib.maintained_selection(): + with maintained_selection(): self.log.debug("instance: {}".format(instance)) self.log.debug("instance.data[families]: {}".format( instance.data["families"])) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 5c30df9a62..97ddef0a59 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -1,7 +1,10 @@ import nuke import pyblish.api from avalon import io, api -from avalon.nuke import lib as anlib +from openpype.hosts.nuke.api.lib import ( + add_publish_knob, + get_avalon_knob_data +) @pyblish.api.log @@ -39,7 +42,7 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): self.log.warning(E) # get data from avalon knob - avalon_knob_data = anlib.get_avalon_knob_data( + avalon_knob_data = get_avalon_knob_data( node, ["avalon:", "ak:"]) self.log.debug("avalon_knob_data: {}".format(avalon_knob_data)) @@ -115,7 +118,7 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): # get publish knob value if "publish" not in node.knobs(): - anlib.add_publish_knob(node) + add_publish_knob(node) # sync workfile version _families_test = [family] + families diff --git a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py index 0e27273ceb..a2d1c80628 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py @@ -1,8 +1,13 @@ -import nuke -import pyblish.api import os + +import nuke + +import pyblish.api import openpype.api as pype -from avalon.nuke import lib as anlib +from openpype.hosts.nuke.api.lib import ( + add_publish_knob, + get_avalon_knob_data +) class CollectWorkfile(pyblish.api.ContextPlugin): @@ -17,9 +22,9 @@ class CollectWorkfile(pyblish.api.ContextPlugin): current_file = os.path.normpath(nuke.root().name()) - knob_data = anlib.get_avalon_knob_data(root) + knob_data = get_avalon_knob_data(root) - anlib.add_publish_knob(root) + add_publish_knob(root) family = "workfile" task = os.getenv("AVALON_TASK", None) diff --git a/openpype/hosts/nuke/plugins/publish/validate_backdrop.py b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py index f280ad4af1..7694c3d2ba 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py @@ -1,6 +1,6 @@ -import pyblish -from avalon.nuke import lib as anlib import nuke +import pyblish +from openpype.hosts.nuke.api.lib import maintained_selection class SelectCenterInNodeGraph(pyblish.api.Action): @@ -28,7 +28,7 @@ class SelectCenterInNodeGraph(pyblish.api.Action): all_yC = list() # maintain selection - with anlib.maintained_selection(): + with maintained_selection(): # collect all failed nodes xpos and ypos for instance in instances: bdn = instance[0] diff --git a/openpype/hosts/nuke/plugins/publish/validate_gizmo.py b/openpype/hosts/nuke/plugins/publish/validate_gizmo.py index 9c94ea88ef..d0d930f50c 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_gizmo.py +++ b/openpype/hosts/nuke/plugins/publish/validate_gizmo.py @@ -1,6 +1,6 @@ -import pyblish -from avalon.nuke import lib as anlib import nuke +import pyblish +from openpype.hosts.nuke.api.lib import maintained_selection class OpenFailedGroupNode(pyblish.api.Action): @@ -25,7 +25,7 @@ class OpenFailedGroupNode(pyblish.api.Action): instances = pyblish.api.instances_by_plugin(failed, plugin) # maintain selection - with anlib.maintained_selection(): + with maintained_selection(): # collect all failed nodes xpos and ypos for instance in instances: grpn = instance[0] diff --git a/openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py b/openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py index ddf46a0873..842f74b6f6 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py +++ b/openpype/hosts/nuke/plugins/publish/validate_instance_in_context.py @@ -6,8 +6,11 @@ import nuke import pyblish.api import openpype.api -import avalon.nuke.lib -import openpype.hosts.nuke.api as nuke_api +from openpype.hosts.nuke.api.lib import ( + recreate_instance, + reset_selection, + select_nodes +) class SelectInvalidInstances(pyblish.api.Action): @@ -47,12 +50,12 @@ class SelectInvalidInstances(pyblish.api.Action): self.deselect() def select(self, instances): - avalon.nuke.lib.select_nodes( + select_nodes( [nuke.toNode(str(x)) for x in instances] ) def deselect(self): - avalon.nuke.lib.reset_selection() + reset_selection() class RepairSelectInvalidInstances(pyblish.api.Action): @@ -82,7 +85,7 @@ class RepairSelectInvalidInstances(pyblish.api.Action): context_asset = context.data["assetEntity"]["name"] for instance in instances: origin_node = instance[0] - nuke_api.lib.recreate_instance( + recreate_instance( origin_node, avalon_data={"asset": context_asset} ) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py index ba34ec8338..a73bed8edd 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py @@ -1,13 +1,12 @@ -import toml import os +import toml import nuke from avalon import api -import re import pyblish.api import openpype.api -from avalon.nuke import get_avalon_knob_data +from openpype.hosts.nuke.api.lib import get_avalon_knob_data class ValidateWriteLegacy(pyblish.api.InstancePlugin): diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 732f321b85..c0d5c8f402 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -1,8 +1,11 @@ import os import pyblish.api import openpype.utils -import openpype.hosts.nuke.lib as nukelib -import avalon.nuke +from openpype.hosts.nuke.api.lib import ( + get_write_node_template_attr, + get_node_path +) + @pyblish.api.log class RepairNukeWriteNodeAction(pyblish.api.Action): @@ -15,7 +18,7 @@ class RepairNukeWriteNodeAction(pyblish.api.Action): for instance in instances: node = instance[1] - correct_data = nukelib.get_write_node_template_attr(node) + correct_data = get_write_node_template_attr(node) for k, v in correct_data.items(): node[k].setValue(v) self.log.info("Node attributes were fixed") @@ -34,14 +37,14 @@ class ValidateNukeWriteNode(pyblish.api.InstancePlugin): def process(self, instance): node = instance[1] - correct_data = nukelib.get_write_node_template_attr(node) + correct_data = get_write_node_template_attr(node) check = [] for k, v in correct_data.items(): if k is 'file': padding = len(v.split('#')) - ref_path = avalon.nuke.lib.get_node_path(v, padding) - n_path = avalon.nuke.lib.get_node_path(node[k].value(), padding) + ref_path = get_node_path(v, padding) + n_path = get_node_path(node[k].value(), padding) isnt = False for i, p in enumerate(ref_path): if str(n_path[i]) not in str(p): diff --git a/openpype/hosts/nuke/startup/init.py b/openpype/hosts/nuke/startup/init.py index 0ea5d1ad7d..d7560814bf 100644 --- a/openpype/hosts/nuke/startup/init.py +++ b/openpype/hosts/nuke/startup/init.py @@ -1,2 +1,4 @@ +import nuke + # default write mov nuke.knobDefault('Write.mov.colorspace', 'sRGB') diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index b7ed35b3b4..2cac6d09e7 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,14 +1,19 @@ +import nuke +import avalon.api + +from openpype.api import Logger +from openpype.hosts.nuke import api from openpype.hosts.nuke.api.lib import ( on_script_load, check_inventory_versions, - WorkfileSettings + WorkfileSettings, + dirmap_file_name_filter ) -import nuke -from openpype.api import Logger -from openpype.hosts.nuke.api.lib import dirmap_file_name_filter +log = Logger.get_logger(__name__) -log = Logger().get_logger(__name__) + +avalon.api.install(api) # fix ffmpeg settings on script nuke.addOnScriptLoad(on_script_load) From 26d8304fd9704f04bd9ac076d193dc1646e4a38b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Jan 2022 12:27:09 +0100 Subject: [PATCH 17/62] removed avalon nuke path from add implementation environments --- openpype/hosts/nuke/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/__init__.py b/openpype/hosts/nuke/__init__.py index 366f704dd8..60b37ce1dd 100644 --- a/openpype/hosts/nuke/__init__.py +++ b/openpype/hosts/nuke/__init__.py @@ -6,10 +6,7 @@ def add_implementation_envs(env, _app): # Add requirements to NUKE_PATH pype_root = os.environ["OPENPYPE_REPOS_ROOT"] new_nuke_paths = [ - os.path.join(pype_root, "openpype", "hosts", "nuke", "startup"), - os.path.join( - pype_root, "repos", "avalon-core", "setup", "nuke", "nuke_path" - ) + os.path.join(pype_root, "openpype", "hosts", "nuke", "startup") ] old_nuke_path = env.get("NUKE_PATH") or "" for path in old_nuke_path.split(os.pathsep): From 9980aa90fa196eb07e57ea7155b7ce98469d81e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Jan 2022 12:32:21 +0100 Subject: [PATCH 18/62] fix default value of function argument --- openpype/hosts/nuke/api/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index f8f248357b..205b23efe6 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -49,12 +49,14 @@ def gizmo_is_nuke_default(gizmo): return gizmo.filename().startswith(plug_dir) -def bake_gizmos_recursively(in_group=nuke.Root()): +def bake_gizmos_recursively(in_group=None): """Converting a gizmo to group Argumets: is_group (nuke.Node)[optonal]: group node or all nodes """ + if in_group is None: + in_group = nuke.Root() # preserve selection after all is done with maintained_selection(): # jump to the group From c01ed46157fe70346a5a6e3b639624fe6ca551b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Jan 2022 14:11:49 +0100 Subject: [PATCH 19/62] added ability to skip 3rd part lib validations --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 92cc76dc7a..6891b3c419 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,9 @@ def validate_thirdparty_binaries(): raise RuntimeError(error_msg.format("OpenImageIO")) -validate_thirdparty_binaries() +# Give ability to skip vaidation +if not os.getenv("SKIP_THIRD_PARTY_VALIDATION"): + validate_thirdparty_binaries() version = {} From 95dcc57d0f175490a73e37852238c875cad1816f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Jan 2022 16:41:51 +0100 Subject: [PATCH 20/62] OP-1730 - add permission to list channels Will be used to delete messages or files --- openpype/modules/slack/manifest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index bd920ac266..7a65cc5915 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -18,6 +18,7 @@ oauth_config: - chat:write.customize - chat:write.public - files:write + - channels:read settings: org_deploy_enabled: false socket_mode_enabled: false From 57a51c28a38a6313f3df08e54acefe69f650227a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Jan 2022 16:43:12 +0100 Subject: [PATCH 21/62] OP-1730 - added icon and documentation Slack requires peculiar format of icon, provided one. --- .../modules/slack/resources/openpype_icon.png | Bin 0 -> 105495 bytes website/docs/module_slack.md | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 openpype/modules/slack/resources/openpype_icon.png diff --git a/openpype/modules/slack/resources/openpype_icon.png b/openpype/modules/slack/resources/openpype_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bb38dcf5778d06b75793b70c8c92176062e8c90b GIT binary patch literal 105495 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≫Lb-DaL0}w^*iUAX- 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

-|1{^JYf%l_9# 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 Basic information > Display Information. +You can upload any image you want, or for your convenience locate prepared OpenPype icon in your installed Openpype installation in `openpype\modules\slac\resources`. ## System Settings From e9cbcf917c1cce6984254cbee7e72e4e163568b0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Jan 2022 16:58:04 +0100 Subject: [PATCH 22/62] OP-1730 - added logging sent messages and uploaded files into db --- .../plugins/publish/integrate_slack_api.py | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 5aba372549..6afbbf5849 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -2,8 +2,10 @@ import os import six import pyblish.api import copy +from datetime import datetime from openpype.lib.plugin_tools import prepare_template_data +from openpype.lib import OpenPypeMongoConnection class IntegrateSlackAPI(pyblish.api.InstancePlugin): @@ -41,23 +43,38 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if not message: return - # if message_profile["upload_thumbnail"] and thumbnail_path: - # publish_files.add(thumbnail_path) + if message_profile["upload_thumbnail"] and thumbnail_path: + publish_files.add(thumbnail_path) if message_profile["upload_review"] and review_path: publish_files.add(review_path) + project = instance.context.data["anatomyData"]["project"]["code"] for channel in message_profile["channels"]: if six.PY2: - self._python2_call(instance.data["slack_token"], - channel, - message, - publish_files) + msg_id, file_ids = \ + self._python2_call(instance.data["slack_token"], + channel, + message, + publish_files) else: - self._python3_call(instance.data["slack_token"], - channel, - message, - publish_files) + msg_id, file_ids = \ + self._python3_call(instance.data["slack_token"], + channel, + message, + publish_files) + + msg = { + "type": "slack", + "msg_id": msg_id, + "file_ids": file_ids, + "project": project, + "created_dt": datetime.now() + } + mongo_client = OpenPypeMongoConnection.get_mongo_client() + database_name = os.environ["OPENPYPE_DATABASE_NAME"] + dbcon = mongo_client[database_name]["notification_messages"] + dbcon.insert_one(msg) def _get_filled_message(self, message_templ, instance, review_path=None): """Use message_templ and data from instance to get message content. @@ -85,7 +102,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): task_data = fill_data.get("task") for key, value in task_data.items(): fill_key = "task[{}]".format(key) - fill_pairs.append((fill_key , value)) + fill_pairs.append((fill_key, value)) fill_pairs.append(("task", task_data["name"])) self.log.debug("fill_pairs ::{}".format(fill_pairs)) @@ -126,23 +143,24 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): break return published_path - def _python2_call(self, token, channel, message, - publish_files): + def _python2_call(self, token, channel, message, publish_files): from slackclient import SlackClient try: client = SlackClient(token) - self.log.info("publish {}".format(publish_files)) attachment_str = "\n\n Attachment links: \n" + file_ids = [] for p_file in publish_files: with open(p_file, 'rb') as pf: response = client.api_call( "files.upload", - channels=channel, - file=pf + file=pf, + channel=channel, + title=os.path.basename(p_file) ) attachment_str += "\n<{}|{}>".format( response["file"]["permalink"], os.path.basename(p_file)) + file_ids.append(response["file"]["id"]) if publish_files: message += attachment_str @@ -152,23 +170,24 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): channel=channel, text=message ) - self.log.info("repsonse {}".format(response)) if response.get("error"): error_str = self._enrich_error(str(response.get("error")), channel) self.log.warning("Error happened: {}".format(error_str)) + else: + return response["ts"], file_ids except Exception as e: # You will get a SlackApiError if "ok" is False error_str = self._enrich_error(str(e), channel) self.log.warning("Error happened: {}".format(error_str)) - def _python3_call(self, token, channel, message, - publish_files): + def _python3_call(self, token, channel, message, publish_files): from slack_sdk import WebClient from slack_sdk.errors import SlackApiError try: client = WebClient(token=token) attachment_str = "\n\n Attachment links: \n" + file_ids = [] for published_file in publish_files: response = client.files_upload( file=published_file, @@ -176,16 +195,16 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): attachment_str += "\n<{}|{}>".format( response["file"]["permalink"], os.path.basename(published_file)) + file_ids.append(response["file"]["id"]) if publish_files: message += attachment_str - _ = client.chat_postMessage( + response = client.chat_postMessage( channel=channel, - text=message, - username=self.bot_user_name, - icon_url=self.icon_url + text=message ) + return response.data["ts"], file_ids except SlackApiError as e: # You will get a SlackApiError if "ok" is False error_str = self._enrich_error(str(e.response["error"]), channel) From 5e8f0e0152df9547caed72d4b401a5fb9200fb30 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Jan 2022 17:02:58 +0100 Subject: [PATCH 23/62] OP-1730 - removed obsolete variables Modification username and icon via message payload doesn't work for both post_method and upload_file. Icon must be set in Slack app configuration. --- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 6afbbf5849..5d014382a3 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -26,10 +26,6 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): optional = True - # internal, not configurable - bot_user_name = "OpenPypeNotifier" - icon_url = "https://openpype.io/img/favicon/favicon.ico" - def process(self, instance): thumbnail_path = self._get_thumbnail_path(instance) review_path = self._get_review_path(instance) From 6df5488ccec9f9621b5fdb00e68791c0fcc7781e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 12 Jan 2022 18:07:31 +0100 Subject: [PATCH 24/62] fix type --- igniter/bootstrap_repos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index db62cbbe91..637f821366 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -762,7 +762,7 @@ class BootstrapRepos: destination = self._move_zip_to_data_dir(temp_zip) - return OpenPypeVersion(version=version, path=destination) + return OpenPypeVersion(version=version, path=Path(destination)) def _move_zip_to_data_dir(self, zip_file) -> Union[None, Path]: """Move zip with OpenPype version to user data directory. From 91930038880fb24c68518f5d135b5eba44bdbf05 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 12 Jan 2022 18:46:44 +0100 Subject: [PATCH 25/62] fix dir/file resolution --- tools/create_zip.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/create_zip.py b/tools/create_zip.py index 32a4d27e8b..2fc351469a 100644 --- a/tools/create_zip.py +++ b/tools/create_zip.py @@ -31,7 +31,9 @@ def main(path): bs = bootstrap_repos.BootstrapRepos(progress_callback=progress) if path: out_path = Path(path) - bs.data_dir = out_path.parent + bs.data_dir = out_path + if out_path.is_file(): + bs.data_dir = out_path.parent _print(f"Creating zip in {bs.data_dir} ...") repo_file = bs.create_version_from_live_code() From 5d9ddca7d0aef5c84d0119fac0396fb2790f3f1c Mon Sep 17 00:00:00 2001 From: "IGOTGAMES\\jesse.d" Date: Wed, 12 Jan 2022 17:25:22 -0800 Subject: [PATCH 26/62] Fixed bug: File list would be 1 file long if node frame range is 2 frames long. --- openpype/hosts/houdini/plugins/publish/collect_frames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index ef77c3230b..8d21794c1b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -37,7 +37,7 @@ class CollectFrames(pyblish.api.InstancePlugin): # Check if frames are bigger than 1 (file collection) # override the result - if end_frame - start_frame > 1: + if end_frame - start_frame > 0: result = self.create_file_list( match, int(start_frame), int(end_frame) ) From 9d9f9514c1bbc25046b16a6c882505b428fa3c61 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 11:02:21 +0100 Subject: [PATCH 27/62] format output arguments with anatomy data --- openpype/plugins/publish/extract_review.py | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index b6c2e49385..be29c7bf9c 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -187,6 +187,7 @@ class ExtractReview(pyblish.api.InstancePlugin): outputs_per_repres = self._get_outputs_per_representations( instance, profile_outputs ) + fill_data = copy.deepcopy(instance.data["anatomyData"]) for repre, outputs in outputs_per_repres: # Check if input should be preconverted before processing # Store original staging dir (it's value may change) @@ -293,7 +294,7 @@ class ExtractReview(pyblish.api.InstancePlugin): try: # temporary until oiiotool is supported cross platform ffmpeg_args = self._ffmpeg_arguments( - output_def, instance, new_repre, temp_data + output_def, instance, new_repre, temp_data, fill_data ) except ZeroDivisionError: if 'exr' in temp_data["origin_repre"]["ext"]: @@ -446,7 +447,9 @@ class ExtractReview(pyblish.api.InstancePlugin): "handles_are_set": handles_are_set } - def _ffmpeg_arguments(self, output_def, instance, new_repre, temp_data): + def _ffmpeg_arguments( + self, output_def, instance, new_repre, temp_data, fill_data + ): """Prepares ffmpeg arguments for expected extraction. Prepares input and output arguments based on output definition and @@ -472,9 +475,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_input_args = [ value for value in _ffmpeg_input_args if value.strip() ] - ffmpeg_output_args = [ - value for value in _ffmpeg_output_args if value.strip() - ] ffmpeg_video_filters = [ value for value in _ffmpeg_video_filters if value.strip() ] @@ -482,6 +482,21 @@ class ExtractReview(pyblish.api.InstancePlugin): value for value in _ffmpeg_audio_filters if value.strip() ] + ffmpeg_output_args = [] + for value in _ffmpeg_output_args: + value = value.strip() + if not value: + continue + try: + value = value.format(**fill_data) + except Exception: + self.log.warning( + "Failed to format ffmpeg argument: {}".format(value), + exc_info=True + ) + pass + ffmpeg_output_args.append(value) + # Prepare input and output filepaths self.input_output_paths(new_repre, output_def, temp_data) From 29445314346e644ea02e1df8f4479ce8b587a32d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 13:02:51 +0100 Subject: [PATCH 28/62] implemented callback warpper for execution in main thread --- openpype/tools/tray/pype_tray.py | 24 ++++++----- openpype/tools/utils/__init__.py | 5 ++- openpype/tools/utils/lib.py | 70 +++++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 12 deletions(-) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index df0238c848..e7ac390c30 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -22,6 +22,7 @@ from openpype.settings import ( ProjectSettings, DefaultsNotDefined ) +from openpype.tools.utils import WrappedCallbackItem from .pype_info_widget import PypeInfoWidget @@ -61,21 +62,24 @@ class TrayManager: if callback: self.execute_in_main_thread(callback) - def execute_in_main_thread(self, callback): - self._main_thread_callbacks.append(callback) + def execute_in_main_thread(self, callback, *args, **kwargs): + if isinstance(callback, WrappedCallbackItem): + item = callback + else: + item = WrappedCallbackItem(callback, *args, **kwargs) + + self._main_thread_callbacks.append(item) + + return item def _main_thread_execution(self): if self._execution_in_progress: return self._execution_in_progress = True - while self._main_thread_callbacks: - try: - callback = self._main_thread_callbacks.popleft() - callback() - except: - self.log.warning( - "Failed to execute {} in main thread".format(callback), - exc_info=True) + for _ in range(len(self._main_thread_callbacks)): + if self._main_thread_callbacks: + item = self._main_thread_callbacks.popleft() + item.execute() self._execution_in_progress = False diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index 4dd6bdd05f..65025ac358 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -6,6 +6,7 @@ from .widgets import ( ) from .error_dialog import ErrorMessageBox +from .lib import WrappedCallbackItem __all__ = ( @@ -14,5 +15,7 @@ __all__ = ( "ClickableFrame", "ExpandBtn", - "ErrorMessageBox" + "ErrorMessageBox", + + "WrappedCallbackItem", ) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 6742df8557..5f3456ae3e 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -9,7 +9,10 @@ import avalon.api from avalon import style from avalon.vendor import qtawesome -from openpype.api import get_project_settings +from openpype.api import ( + get_project_settings, + Logger +) from openpype.lib import filter_profiles @@ -598,3 +601,68 @@ def is_remove_site_loader(loader): def is_add_site_loader(loader): return hasattr(loader, "add_site_to_representation") + + +class WrappedCallbackItem: + """Structure to store information about callback and args/kwargs for it. + + Item can be used to execute callback in main thread which may be needed + for execution of Qt objects. + + Item store callback (callable variable), arguments and keyword arguments + for the callback. Item hold information about it's process. + """ + not_set = object() + _log = None + + def __init__(self, callback, *args, **kwargs): + self._done = False + self._exception = self.not_set + self._result = self.not_set + self._callback = callback + self._args = args + self._kwargs = kwargs + + def __call__(self): + self.execute() + + @property + def log(self): + cls = self.__class__ + if cls._log is None: + cls._log = Logger.get_logger(cls.__name__) + return cls._log + + @property + def done(self): + return self._done + + @property + def exception(self): + return self._exception + + @property + def result(self): + return self._result + + def execute(self): + """Execute callback and store it's result. + + Method must be called from main thread. Item is marked as `done` + when callback execution finished. Store output of callback of exception + information when callback raise one. + """ + if self.done: + self.log.warning("- item is already processed") + return + + self.log.debug("Running callback: {}".format(str(self._callback))) + try: + result = self._callback(*self._args, **self._kwargs) + self._result = result + + except Exception as exc: + self._exception = exc + + finally: + self._done = True From d7fb171f101bf36495a106fbca296515f692b080 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 13:08:35 +0100 Subject: [PATCH 29/62] added check if current running openpype has expected version --- openpype/lib/__init__.py | 6 ++- openpype/lib/pype_info.py | 48 +++++++++++++++++---- openpype/tools/tray/pype_tray.py | 74 ++++++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 13 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 12e47a8961..65019f3fab 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -170,7 +170,9 @@ from .editorial import ( from .pype_info import ( get_openpype_version, - get_build_version + get_build_version, + is_running_from_build, + is_current_version_studio_latest ) terminal = Terminal @@ -304,4 +306,6 @@ __all__ = [ "get_openpype_version", "get_build_version", + "is_running_from_build", + "is_current_version_studio_latest", ] diff --git a/openpype/lib/pype_info.py b/openpype/lib/pype_info.py index 15856bfb19..ea804c8a18 100644 --- a/openpype/lib/pype_info.py +++ b/openpype/lib/pype_info.py @@ -10,6 +10,12 @@ from openpype.settings.lib import get_local_settings from .execute import get_openpype_execute_args from .local_settings import get_local_site_id from .python_module_tools import import_filepath +from .openpype_version import ( + op_version_control_available, + openpype_path_is_accessible, + get_expected_studio_version, + get_OpenPypeVersion +) def get_openpype_version(): @@ -17,15 +23,6 @@ def get_openpype_version(): return openpype.version.__version__ -def get_pype_version(): - """Backwards compatibility. Remove when 100% not used.""" - print(( - "Using deprecated function 'openpype.lib.pype_info.get_pype_version'" - " replace with 'openpype.lib.pype_info.get_openpype_version'." - )) - return get_openpype_version() - - def get_build_version(): """OpenPype version of build.""" # Return OpenPype version if is running from code @@ -138,3 +135,36 @@ def extract_pype_info_to_file(dirpath): with open(filepath, "w") as file_stream: json.dump(data, file_stream, indent=4) return filepath + + +def is_current_version_studio_latest(): + """Is currently running OpenPype version which is defined by studio. + + It is not recommended to ask in each process as there may be situations + when older OpenPype should be used. For example on farm. But it does make + sense in processes that can run for a long time. + + Returns: + None: Can't determine. e.g. when running from code or the build is + too old. + bool: True when is using studio + """ + output = None + # Skip if is not running from build + if not is_running_from_build(): + return output + + # Skip if build does not support version control + if not op_version_control_available(): + return output + + # Skip if path to folder with zip files is not accessible + if not openpype_path_is_accessible(): + return output + + # Check if current version is expected version + OpenPypeVersion = get_OpenPypeVersion() + current_version = OpenPypeVersion(get_openpype_version()) + expected_version = get_expected_studio_version(is_running_staging()) + + return current_version == expected_version diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index e7ac390c30..5af82b2c64 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -14,7 +14,11 @@ from openpype.api import ( resources, get_system_settings ) -from openpype.lib import get_openpype_execute_args +from openpype.lib import ( + get_openpype_execute_args, + is_current_version_studio_latest, + is_running_from_build +) from openpype.modules import TrayModulesManager from openpype import style from openpype.settings import ( @@ -27,11 +31,43 @@ from openpype.tools.utils import WrappedCallbackItem from .pype_info_widget import PypeInfoWidget +class VersionDialog(QtWidgets.QDialog): + def __init__(self, parent=None): + super(VersionDialog, self).__init__(parent) + + label_widget = QtWidgets.QLabel( + "Your version does not match to studio version", self + ) + + ignore_btn = QtWidgets.QPushButton("Ignore", self) + restart_btn = QtWidgets.QPushButton("Restart and Install", self) + + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.addStretch(1) + btns_layout.addWidget(ignore_btn, 0) + btns_layout.addWidget(restart_btn, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(label_widget, 0) + layout.addStretch(1) + layout.addLayout(btns_layout, 0) + + ignore_btn.clicked.connect(self._on_ignore) + restart_btn.clicked.connect(self._on_reset) + + def _on_ignore(self): + self.reject() + + def _on_reset(self): + self.accept() + + class TrayManager: """Cares about context of application. Load submenus, actions, separators and modules into tray's context. """ + _version_check_interval = 5 * 60 * 1000 def __init__(self, tray_widget, main_window): self.tray_widget = tray_widget @@ -46,6 +82,9 @@ class TrayManager: self.errors = [] + self._version_check_timer = None + self._version_dialog = None + self.main_thread_timer = None self._main_thread_callbacks = collections.deque() self._execution_in_progress = None @@ -62,6 +101,24 @@ class TrayManager: if callback: self.execute_in_main_thread(callback) + def _on_version_check_timer(self): + # Check if is running from build and stop future validations if yes + if not is_running_from_build(): + self._version_check_timer.stop() + return + + self.validate_openpype_version() + + def validate_openpype_version(self): + if is_current_version_studio_latest(): + return + + if self._version_dialog is None: + self._version_dialog = VersionDialog() + result = self._version_dialog.exec_() + if result: + self.restart() + def execute_in_main_thread(self, callback, *args, **kwargs): if isinstance(callback, WrappedCallbackItem): item = callback @@ -123,6 +180,12 @@ class TrayManager: self.main_thread_timer = main_thread_timer + version_check_timer = QtCore.QTimer() + version_check_timer.setInterval(self._version_check_interval) + version_check_timer.timeout.connect(self._on_version_check_timer) + version_check_timer.start() + self._version_check_timer = version_check_timer + # For storing missing settings dialog self._settings_validation_dialog = None @@ -207,7 +270,7 @@ class TrayManager: self.tray_widget.menu.addAction(version_action) self.tray_widget.menu.addSeparator() - def restart(self): + def restart(self, reset_version=True): """Restart Tray tool. First creates new process with same argument and close current tray. @@ -221,7 +284,9 @@ class TrayManager: additional_args.pop(0) args.extend(additional_args) - kwargs = {} + kwargs = { + "env": dict(os.environ.items()) + } if platform.system().lower() == "windows": flags = ( subprocess.CREATE_NEW_PROCESS_GROUP @@ -229,6 +294,9 @@ class TrayManager: ) kwargs["creationflags"] = flags + if reset_version: + kwargs["env"].pop("OPENPYPE_VERSION", None) + subprocess.Popen(args, **kwargs) self.exit() From 7d283f55558f203cd98265ec327be6e2caa5fd53 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 17:08:37 +0100 Subject: [PATCH 30/62] moved code from pype_info to openpype_version and fixed few bugs --- openpype/lib/__init__.py | 2 +- openpype/lib/openpype_version.py | 117 ++++++++++++++++++++++++++++++- openpype/lib/pype_info.py | 89 +---------------------- openpype/resources/__init__.py | 2 +- 4 files changed, 118 insertions(+), 92 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 65019f3fab..c556f2adc1 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -168,7 +168,7 @@ from .editorial import ( make_sequence_collection ) -from .pype_info import ( +from .openpype_version import ( get_openpype_version, get_build_version, is_running_from_build, diff --git a/openpype/lib/openpype_version.py b/openpype/lib/openpype_version.py index e3a4e1fa3e..839222018c 100644 --- a/openpype/lib/openpype_version.py +++ b/openpype/lib/openpype_version.py @@ -9,9 +9,69 @@ OpenPype version located in build but versions available in remote versions repository or locally available. """ +import os import sys +import openpype.version +from .python_module_tools import import_filepath + + +# ---------------------------------------- +# Functions independent on OpenPypeVersion +# ---------------------------------------- +def get_openpype_version(): + """Version of pype that is currently used.""" + return openpype.version.__version__ + + +def get_build_version(): + """OpenPype version of build.""" + # Return OpenPype version if is running from code + if not is_running_from_build(): + return get_openpype_version() + + # Import `version.py` from build directory + version_filepath = os.path.join( + os.environ["OPENPYPE_ROOT"], + "openpype", + "version.py" + ) + if not os.path.exists(version_filepath): + return None + + module = import_filepath(version_filepath, "openpype_build_version") + return getattr(module, "__version__", None) + + +def is_running_from_build(): + """Determine if current process is running from build or code. + + Returns: + bool: True if running from build. + """ + executable_path = os.environ["OPENPYPE_EXECUTABLE"] + executable_filename = os.path.basename(executable_path) + if "python" in executable_filename.lower(): + return False + return True + + +def is_running_staging(): + """Currently used OpenPype is staging version. + + Returns: + bool: True if openpype version containt 'staging'. + """ + if "staging" in get_openpype_version(): + return True + return False + + +# ---------------------------------------- +# Functions dependent on OpenPypeVersion +# - Make sense to call only in OpenPype process +# ---------------------------------------- def get_OpenPypeVersion(): """Access to OpenPypeVersion class stored in sys modules.""" return sys.modules.get("OpenPypeVersion") @@ -71,15 +131,66 @@ def get_remote_versions(*args, **kwargs): return None -def get_latest_version(*args, **kwargs): +def get_latest_version(staging=None, local=None, remote=None): """Get latest version from repository path.""" + if staging is None: + staging = is_running_staging() if op_version_control_available(): - return get_OpenPypeVersion().get_latest_version(*args, **kwargs) + return get_OpenPypeVersion().get_latest_version( + staging=staging, + local=local, + remote=remote + ) return None -def get_expected_studio_version(staging=False): +def get_expected_studio_version(staging=None): """Expected production or staging version in studio.""" + if staging is None: + staging = is_running_staging() if op_version_control_available(): return get_OpenPypeVersion().get_expected_studio_version(staging) return None + + +def is_current_version_studio_latest(): + """Is currently running OpenPype version which is defined by studio. + + It is not recommended to ask in each process as there may be situations + when older OpenPype should be used. For example on farm. But it does make + sense in processes that can run for a long time. + + Returns: + None: Can't determine. e.g. when running from code or the build is + too old. + bool: True when is using studio + """ + output = None + # Skip if is not running from build + if not is_running_from_build(): + return output + + # Skip if build does not support version control + if not op_version_control_available(): + return output + + # Skip if path to folder with zip files is not accessible + if not openpype_path_is_accessible(): + return output + + # Get OpenPypeVersion class + OpenPypeVersion = get_OpenPypeVersion() + # Convert current version to OpenPypeVersion object + current_version = OpenPypeVersion(version=get_openpype_version()) + + staging = is_running_staging() + # Get expected version (from settings) + expected_version = get_expected_studio_version(staging) + if expected_version is None: + # Look for latest if expected version is not set in settings + expected_version = get_latest_version( + staging=staging, + remote=True + ) + # Check if current version is expected version + return current_version == expected_version diff --git a/openpype/lib/pype_info.py b/openpype/lib/pype_info.py index ea804c8a18..848a505187 100644 --- a/openpype/lib/pype_info.py +++ b/openpype/lib/pype_info.py @@ -5,67 +5,15 @@ import platform import getpass import socket -import openpype.version from openpype.settings.lib import get_local_settings from .execute import get_openpype_execute_args from .local_settings import get_local_site_id -from .python_module_tools import import_filepath from .openpype_version import ( - op_version_control_available, - openpype_path_is_accessible, - get_expected_studio_version, - get_OpenPypeVersion + is_running_from_build, + get_openpype_version ) -def get_openpype_version(): - """Version of pype that is currently used.""" - return openpype.version.__version__ - - -def get_build_version(): - """OpenPype version of build.""" - # Return OpenPype version if is running from code - if not is_running_from_build(): - return get_openpype_version() - - # Import `version.py` from build directory - version_filepath = os.path.join( - os.environ["OPENPYPE_ROOT"], - "openpype", - "version.py" - ) - if not os.path.exists(version_filepath): - return None - - module = import_filepath(version_filepath, "openpype_build_version") - return getattr(module, "__version__", None) - - -def is_running_from_build(): - """Determine if current process is running from build or code. - - Returns: - bool: True if running from build. - """ - executable_path = os.environ["OPENPYPE_EXECUTABLE"] - executable_filename = os.path.basename(executable_path) - if "python" in executable_filename.lower(): - return False - return True - - -def is_running_staging(): - """Currently used OpenPype is staging version. - - Returns: - bool: True if openpype version containt 'staging'. - """ - if "staging" in get_openpype_version(): - return True - return False - - def get_pype_info(): """Information about currently used Pype process.""" executable_args = get_openpype_execute_args() @@ -135,36 +83,3 @@ def extract_pype_info_to_file(dirpath): with open(filepath, "w") as file_stream: json.dump(data, file_stream, indent=4) return filepath - - -def is_current_version_studio_latest(): - """Is currently running OpenPype version which is defined by studio. - - It is not recommended to ask in each process as there may be situations - when older OpenPype should be used. For example on farm. But it does make - sense in processes that can run for a long time. - - Returns: - None: Can't determine. e.g. when running from code or the build is - too old. - bool: True when is using studio - """ - output = None - # Skip if is not running from build - if not is_running_from_build(): - return output - - # Skip if build does not support version control - if not op_version_control_available(): - return output - - # Skip if path to folder with zip files is not accessible - if not openpype_path_is_accessible(): - return output - - # Check if current version is expected version - OpenPypeVersion = get_OpenPypeVersion() - current_version = OpenPypeVersion(get_openpype_version()) - expected_version = get_expected_studio_version(is_running_staging()) - - return current_version == expected_version diff --git a/openpype/resources/__init__.py b/openpype/resources/__init__.py index f463933525..34a833d080 100644 --- a/openpype/resources/__init__.py +++ b/openpype/resources/__init__.py @@ -1,5 +1,5 @@ import os -from openpype.lib.pype_info import is_running_staging +from openpype.lib.openpype_version import is_running_staging RESOURCES_DIR = os.path.dirname(os.path.abspath(__file__)) From 12156b6d90f723d6a96016fb51c3e876415dca8c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 17:57:27 +0100 Subject: [PATCH 31/62] tray will show info that is outdated and user should restart --- openpype/lib/__init__.py | 2 + openpype/lib/openpype_version.py | 37 ++++++------ openpype/style/data.json | 6 +- openpype/style/style.css | 8 +-- .../project_manager/project_manager/style.py | 2 +- .../project_manager/widgets.py | 2 +- openpype/tools/tray/pype_tray.py | 58 ++++++++++++++++--- 7 files changed, 81 insertions(+), 34 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index c556f2adc1..a2a16bcc00 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -171,6 +171,7 @@ from .editorial import ( from .openpype_version import ( get_openpype_version, get_build_version, + get_expected_version, is_running_from_build, is_current_version_studio_latest ) @@ -306,6 +307,7 @@ __all__ = [ "get_openpype_version", "get_build_version", + "get_expected_version", "is_running_from_build", "is_current_version_studio_latest", ] diff --git a/openpype/lib/openpype_version.py b/openpype/lib/openpype_version.py index 839222018c..201bf646e9 100644 --- a/openpype/lib/openpype_version.py +++ b/openpype/lib/openpype_version.py @@ -153,6 +153,17 @@ def get_expected_studio_version(staging=None): return None +def get_expected_version(staging=None): + expected_version = get_expected_studio_version(staging) + if expected_version is None: + # Look for latest if expected version is not set in settings + expected_version = get_latest_version( + staging=staging, + remote=True + ) + return expected_version + + def is_current_version_studio_latest(): """Is currently running OpenPype version which is defined by studio. @@ -166,16 +177,13 @@ def is_current_version_studio_latest(): bool: True when is using studio """ output = None - # Skip if is not running from build - if not is_running_from_build(): - return output - - # Skip if build does not support version control - if not op_version_control_available(): - return output - - # Skip if path to folder with zip files is not accessible - if not openpype_path_is_accessible(): + # Skip if is not running from build or build does not support version + # control or path to folder with zip files is not accessible + if ( + not is_running_from_build() + or not op_version_control_available() + or not openpype_path_is_accessible() + ): return output # Get OpenPypeVersion class @@ -183,14 +191,7 @@ def is_current_version_studio_latest(): # Convert current version to OpenPypeVersion object current_version = OpenPypeVersion(version=get_openpype_version()) - staging = is_running_staging() # Get expected version (from settings) - expected_version = get_expected_studio_version(staging) - if expected_version is None: - # Look for latest if expected version is not set in settings - expected_version = get_latest_version( - staging=staging, - remote=True - ) + expected_version = get_expected_version() # Check if current version is expected version return current_version == expected_version diff --git a/openpype/style/data.json b/openpype/style/data.json index b3dffd7c71..6e1b6e822b 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -51,8 +51,10 @@ "border-hover": "rgba(168, 175, 189, .3)", "border-focus": "rgb(92, 173, 214)", - "delete-btn-bg": "rgb(201, 54, 54)", - "delete-btn-bg-disabled": "rgba(201, 54, 54, 64)", + "warning-btn-bg": "rgb(201, 54, 54)", + + "warning-btn-bg": "rgb(201, 54, 54)", + "warning-btn-bg-disabled": "rgba(201, 54, 54, 64)", "tab-widget": { "bg": "#21252B", diff --git a/openpype/style/style.css b/openpype/style/style.css index 7f7f30e2bc..65e8d0cb40 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -734,11 +734,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: {color:bg-view-hover}; } -#DeleteButton { - background: {color:delete-btn-bg}; +#WarningButton { + background: {color:warning-btn-bg}; } -#DeleteButton:disabled { - background: {color:delete-btn-bg-disabled}; +#WarningButton:disabled { + background: {color:warning-btn-bg-disabled}; } /* Launcher specific stylesheets */ diff --git a/openpype/tools/project_manager/project_manager/style.py b/openpype/tools/project_manager/project_manager/style.py index 9fa7a5520b..980c637bca 100644 --- a/openpype/tools/project_manager/project_manager/style.py +++ b/openpype/tools/project_manager/project_manager/style.py @@ -95,7 +95,7 @@ class ResourceCache: def get_warning_pixmap(cls): src_image = get_warning_image() colors = get_objected_colors() - color_value = colors["delete-btn-bg"] + color_value = colors["warning-btn-bg"] return paint_image_with_color( src_image, diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 4b5aca35ef..e58dcc7d0c 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -369,7 +369,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog): cancel_btn = QtWidgets.QPushButton("Cancel", self) cancel_btn.setToolTip("Cancel deletion of the project") confirm_btn = QtWidgets.QPushButton("Permanently Delete Project", self) - confirm_btn.setObjectName("DeleteButton") + confirm_btn.setObjectName("WarningButton") confirm_btn.setEnabled(False) confirm_btn.setToolTip("Confirm deletion") diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 5af82b2c64..c32cf17e18 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -17,7 +17,9 @@ from openpype.api import ( from openpype.lib import ( get_openpype_execute_args, is_current_version_studio_latest, - is_running_from_build + is_running_from_build, + get_expected_version, + get_openpype_version ) from openpype.modules import TrayModulesManager from openpype import style @@ -32,15 +34,30 @@ from .pype_info_widget import PypeInfoWidget class VersionDialog(QtWidgets.QDialog): + restart_requested = QtCore.Signal() + + _min_width = 400 + _min_height = 130 + def __init__(self, parent=None): super(VersionDialog, self).__init__(parent) - - label_widget = QtWidgets.QLabel( - "Your version does not match to studio version", self + self.setWindowTitle("Wrong OpenPype version") + icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) + self.setWindowIcon(icon) + self.setWindowFlags( + self.windowFlags() + | QtCore.Qt.WindowStaysOnTopHint ) + self.setMinimumWidth(self._min_width) + self.setMinimumHeight(self._min_height) + + label_widget = QtWidgets.QLabel(self) + label_widget.setWordWrap(True) + ignore_btn = QtWidgets.QPushButton("Ignore", self) - restart_btn = QtWidgets.QPushButton("Restart and Install", self) + ignore_btn.setObjectName("WarningButton") + restart_btn = QtWidgets.QPushButton("Restart and Change", self) btns_layout = QtWidgets.QHBoxLayout() btns_layout.addStretch(1) @@ -55,10 +72,22 @@ class VersionDialog(QtWidgets.QDialog): ignore_btn.clicked.connect(self._on_ignore) restart_btn.clicked.connect(self._on_reset) + self._label_widget = label_widget + + self.setStyleSheet(style.load_stylesheet()) + + def update_versions(self, current_version, expected_version): + message = ( + "Your OpenPype version {} does" + " not match to studio version {}" + ).format(str(current_version), str(expected_version)) + self._label_widget.setText(message) + def _on_ignore(self): self.reject() def _on_reset(self): + self.restart_requested.emit() self.accept() @@ -115,9 +144,22 @@ class TrayManager: if self._version_dialog is None: self._version_dialog = VersionDialog() - result = self._version_dialog.exec_() - if result: - self.restart() + self._version_dialog.restart_requested.connect( + self._restart_and_install + ) + + if self._version_dialog.isVisible(): + return + + expected_version = get_expected_version() + current_version = get_openpype_version() + self._version_dialog.update_versions( + current_version, expected_version + ) + self._version_dialog.exec_() + + def _restart_and_install(self): + self.restart() def execute_in_main_thread(self, callback, *args, **kwargs): if isinstance(callback, WrappedCallbackItem): From 644711c9d61f34e83b5f821d833c597b032a37b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 18:16:23 +0100 Subject: [PATCH 32/62] status action gives information about openpype version --- .../ftrack/scripts/sub_event_status.py | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/openpype/modules/default_modules/ftrack/scripts/sub_event_status.py b/openpype/modules/default_modules/ftrack/scripts/sub_event_status.py index 004f61338c..3163642e3f 100644 --- a/openpype/modules/default_modules/ftrack/scripts/sub_event_status.py +++ b/openpype/modules/default_modules/ftrack/scripts/sub_event_status.py @@ -16,8 +16,14 @@ from openpype_modules.ftrack.ftrack_server.lib import ( TOPIC_STATUS_SERVER_RESULT ) from openpype.api import Logger +from openpype.lib import ( + is_current_version_studio_latest, + is_running_from_build, + get_expected_version, + get_openpype_version +) -log = Logger().get_logger("Event storer") +log = Logger.get_logger("Event storer") action_identifier = ( "event.server.status" + os.environ["FTRACK_EVENT_SUB_ID"] ) @@ -203,8 +209,57 @@ class StatusFactory: }) return items + def openpype_version_items(self): + items = [] + is_latest = is_current_version_studio_latest() + items.append({ + "type": "label", + "value": "# OpenPype version" + }) + if not is_running_from_build(): + items.append({ + "type": "label", + "value": ( + "OpenPype event server is running from code {}." + ).format(str(get_openpype_version())) + }) + + elif is_latest is None: + items.append({ + "type": "label", + "value": ( + "Can't determine if OpenPype version is outdated" + " {}. OpenPype build version should be updated." + ).format(str(get_openpype_version())) + }) + elif is_latest: + items.append({ + "type": "label", + "value": "OpenPype version is up to date {}.".format( + str(get_openpype_version()) + ) + }) + else: + items.append({ + "type": "label", + "value": ( + "Using outdated OpenPype version {}." + " Expected version is {}." + "
- Please restart event server for automatic" + " updates or update manually." + ).format( + str(get_openpype_version()), + str(get_expected_version()) + ) + }) + + items.append({"type": "label", "value": "---"}) + + return items + def items(self): items = [] + items.extend(self.openpype_version_items()) items.append(self.note_item) items.extend(self.bool_items()) From 4ee86a6ce27f0a56b88926719c61bab308e5c144 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 18:26:13 +0100 Subject: [PATCH 33/62] show tray message when update dialog is ignored --- openpype/tools/tray/pype_tray.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index c32cf17e18..17251b404f 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -35,6 +35,7 @@ from .pype_info_widget import PypeInfoWidget class VersionDialog(QtWidgets.QDialog): restart_requested = QtCore.Signal() + ignore_requested = QtCore.Signal() _min_width = 400 _min_height = 130 @@ -73,9 +74,19 @@ class VersionDialog(QtWidgets.QDialog): restart_btn.clicked.connect(self._on_reset) self._label_widget = label_widget + self._restart_accepted = False self.setStyleSheet(style.load_stylesheet()) + def showEvent(self, event): + super().showEvent(event) + self._restart_accepted = False + + def closeEvent(self, event): + super().closeEvent(event) + if not self._restart_accepted: + self.ignore_requested.emit() + def update_versions(self, current_version, expected_version): message = ( "Your OpenPype version {} does" @@ -87,6 +98,7 @@ class VersionDialog(QtWidgets.QDialog): self.reject() def _on_reset(self): + self._restart_accepted = True self.restart_requested.emit() self.accept() @@ -147,6 +159,9 @@ class TrayManager: self._version_dialog.restart_requested.connect( self._restart_and_install ) + self._version_dialog.ignore_requested.connect( + self._outdated_version_ignored + ) if self._version_dialog.isVisible(): return @@ -161,6 +176,15 @@ class TrayManager: def _restart_and_install(self): self.restart() + def _outdated_version_ignored(self): + self.show_tray_message( + "Outdated OpenPype version", + ( + "Please update your OpenPype as soon as possible." + " All you have to do is to restart tray." + ) + ) + def execute_in_main_thread(self, callback, *args, **kwargs): if isinstance(callback, WrappedCallbackItem): item = callback From 687181e3825373894111f8a6267ad9fd9fe99917 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Jan 2022 10:58:36 +0100 Subject: [PATCH 34/62] interval of validation can be modified --- .../defaults/system_settings/general.json | 1 + .../schemas/system_schema/schema_general.json | 13 +++++++++++++ openpype/tools/tray/pype_tray.py | 17 ++++++++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/openpype/settings/defaults/system_settings/general.json b/openpype/settings/defaults/system_settings/general.json index a07152eaf8..7c78de9a5c 100644 --- a/openpype/settings/defaults/system_settings/general.json +++ b/openpype/settings/defaults/system_settings/general.json @@ -4,6 +4,7 @@ "admin_password": "", "production_version": "", "staging_version": "", + "version_check_interval": 5, "environment": { "__environment_keys__": { "global": [] diff --git a/openpype/settings/entities/schemas/system_schema/schema_general.json b/openpype/settings/entities/schemas/system_schema/schema_general.json index b4c83fc85f..3af3f5ce35 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_general.json +++ b/openpype/settings/entities/schemas/system_schema/schema_general.json @@ -47,6 +47,19 @@ { "type": "splitter" }, + { + "type": "label", + "label": "Trigger validation if running OpenPype is using studio defined version each 'n' minutes. Validation happens in OpenPype tray application." + }, + { + "type": "number", + "key": "version_check_interval", + "label": "Version check interval", + "minimum": 0 + }, + { + "type": "splitter" + }, { "key": "environment", "label": "Environment", diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 17251b404f..de1a8577b0 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -108,8 +108,6 @@ class TrayManager: Load submenus, actions, separators and modules into tray's context. """ - _version_check_interval = 5 * 60 * 1000 - def __init__(self, tray_widget, main_window): self.tray_widget = tray_widget self.main_window = main_window @@ -117,7 +115,15 @@ class TrayManager: self.log = Logger.get_logger(self.__class__.__name__) - self.module_settings = get_system_settings()["modules"] + system_settings = get_system_settings() + self.module_settings = system_settings["modules"] + + version_check_interval = system_settings["general"].get( + "version_check_interval" + ) + if version_check_interval is None: + version_check_interval = 5 + self._version_check_interval = version_check_interval * 60 * 1000 self.modules_manager = TrayModulesManager() @@ -247,9 +253,10 @@ class TrayManager: self.main_thread_timer = main_thread_timer version_check_timer = QtCore.QTimer() - version_check_timer.setInterval(self._version_check_interval) version_check_timer.timeout.connect(self._on_version_check_timer) - version_check_timer.start() + if self._version_check_interval > 0: + version_check_timer.setInterval(self._version_check_interval) + version_check_timer.start() self._version_check_timer = version_check_timer # For storing missing settings dialog From 0ebd7881c144a16e98a8923c4f5e2f8ea22a355e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Jan 2022 11:40:38 +0100 Subject: [PATCH 35/62] fixed reseting from staging --- openpype/tools/tray/pype_tray.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index de1a8577b0..7f78140211 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -18,6 +18,7 @@ from openpype.lib import ( get_openpype_execute_args, is_current_version_studio_latest, is_running_from_build, + is_running_staging, get_expected_version, get_openpype_version ) @@ -349,17 +350,25 @@ class TrayManager: First creates new process with same argument and close current tray. """ args = get_openpype_execute_args() + kwargs = { + "env": dict(os.environ.items()) + } + # Create a copy of sys.argv additional_args = list(sys.argv) # Check last argument from `get_openpype_execute_args` # - when running from code it is the same as first from sys.argv if args[-1] == additional_args[0]: additional_args.pop(0) - args.extend(additional_args) - kwargs = { - "env": dict(os.environ.items()) - } + # Pop OPENPYPE_VERSION + if reset_version: + # Add staging flag if was running from staging + if is_running_staging(): + args.append("--use-staging") + kwargs["env"].pop("OPENPYPE_VERSION", None) + + args.extend(additional_args) if platform.system().lower() == "windows": flags = ( subprocess.CREATE_NEW_PROCESS_GROUP @@ -367,9 +376,6 @@ class TrayManager: ) kwargs["creationflags"] = flags - if reset_version: - kwargs["env"].pop("OPENPYPE_VERSION", None) - subprocess.Popen(args, **kwargs) self.exit() From 438d6df439cbec5ebe9da311dbad4a6cda7144d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Jan 2022 15:56:36 +0100 Subject: [PATCH 36/62] burnins fix bit rate for dnxhd mxf passing metadata to burnins --- openpype/scripts/otio_burnin.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 3fc1412e62..639657d68f 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -157,6 +157,16 @@ def _dnxhd_codec_args(stream_data, source_ffmpeg_cmd): if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) + # Use arguments from source if are available source arguments + if source_ffmpeg_cmd: + copy_args = ( + "-b:v", "-vb", + ) + args = source_ffmpeg_cmd.split(" ") + for idx, arg in enumerate(args): + if arg in copy_args: + output.extend([arg, args[idx + 1]]) + output.extend(["-g", "1"]) return output @@ -716,6 +726,15 @@ def burnins_from_data( ffmpeg_args.extend( get_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd) ) + # Use arguments from source if are available source arguments + if source_ffmpeg_cmd: + copy_args = ( + "-metadata", + ) + args = source_ffmpeg_cmd.split(" ") + for idx, arg in enumerate(args): + if arg in copy_args: + ffmpeg_args.extend([arg, args[idx + 1]]) # Use group one (same as `-intra` argument, which is deprecated) ffmpeg_args_str = " ".join(ffmpeg_args) From 1150de03b307105f39d99a6f96ec8cab5a0ccb2b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Jan 2022 11:02:21 +0100 Subject: [PATCH 37/62] format output arguments with anatomy data --- openpype/plugins/publish/extract_review.py | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index b6c2e49385..be29c7bf9c 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -187,6 +187,7 @@ class ExtractReview(pyblish.api.InstancePlugin): outputs_per_repres = self._get_outputs_per_representations( instance, profile_outputs ) + fill_data = copy.deepcopy(instance.data["anatomyData"]) for repre, outputs in outputs_per_repres: # Check if input should be preconverted before processing # Store original staging dir (it's value may change) @@ -293,7 +294,7 @@ class ExtractReview(pyblish.api.InstancePlugin): try: # temporary until oiiotool is supported cross platform ffmpeg_args = self._ffmpeg_arguments( - output_def, instance, new_repre, temp_data + output_def, instance, new_repre, temp_data, fill_data ) except ZeroDivisionError: if 'exr' in temp_data["origin_repre"]["ext"]: @@ -446,7 +447,9 @@ class ExtractReview(pyblish.api.InstancePlugin): "handles_are_set": handles_are_set } - def _ffmpeg_arguments(self, output_def, instance, new_repre, temp_data): + def _ffmpeg_arguments( + self, output_def, instance, new_repre, temp_data, fill_data + ): """Prepares ffmpeg arguments for expected extraction. Prepares input and output arguments based on output definition and @@ -472,9 +475,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_input_args = [ value for value in _ffmpeg_input_args if value.strip() ] - ffmpeg_output_args = [ - value for value in _ffmpeg_output_args if value.strip() - ] ffmpeg_video_filters = [ value for value in _ffmpeg_video_filters if value.strip() ] @@ -482,6 +482,21 @@ class ExtractReview(pyblish.api.InstancePlugin): value for value in _ffmpeg_audio_filters if value.strip() ] + ffmpeg_output_args = [] + for value in _ffmpeg_output_args: + value = value.strip() + if not value: + continue + try: + value = value.format(**fill_data) + except Exception: + self.log.warning( + "Failed to format ffmpeg argument: {}".format(value), + exc_info=True + ) + pass + ffmpeg_output_args.append(value) + # Prepare input and output filepaths self.input_output_paths(new_repre, output_def, temp_data) From eed31e543372d3616433aa08d8250993fbc7d0e2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 14 Jan 2022 15:56:36 +0100 Subject: [PATCH 38/62] burnins fix bit rate for dnxhd mxf passing metadata to burnins --- openpype/scripts/otio_burnin.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 15a62ef38e..63a8b064db 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -157,6 +157,16 @@ def _dnxhd_codec_args(stream_data, source_ffmpeg_cmd): if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) + # Use arguments from source if are available source arguments + if source_ffmpeg_cmd: + copy_args = ( + "-b:v", "-vb", + ) + args = source_ffmpeg_cmd.split(" ") + for idx, arg in enumerate(args): + if arg in copy_args: + output.extend([arg, args[idx + 1]]) + output.extend(["-g", "1"]) return output @@ -715,6 +725,15 @@ def burnins_from_data( ffmpeg_args.extend( get_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd) ) + # Use arguments from source if are available source arguments + if source_ffmpeg_cmd: + copy_args = ( + "-metadata", + ) + args = source_ffmpeg_cmd.split(" ") + for idx, arg in enumerate(args): + if arg in copy_args: + ffmpeg_args.extend([arg, args[idx + 1]]) # Use group one (same as `-intra` argument, which is deprecated) ffmpeg_args_str = " ".join(ffmpeg_args) From 17578c54471ad931bc48afc82b5aa8ccd4a29908 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Jan 2022 16:20:05 +0100 Subject: [PATCH 39/62] fix import if 'is_running_staging' --- openpype/lib/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index a2a16bcc00..62d204186d 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -173,6 +173,7 @@ from .openpype_version import ( get_build_version, get_expected_version, is_running_from_build, + is_running_staging, is_current_version_studio_latest ) @@ -309,5 +310,6 @@ __all__ = [ "get_build_version", "get_expected_version", "is_running_from_build", + "is_running_staging", "is_current_version_studio_latest", ] From ce5c70e28d99d1528ab12e75be91c1a219b38aae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Jan 2022 17:47:11 +0100 Subject: [PATCH 40/62] change back project manager styles --- openpype/style/data.json | 5 ++--- openpype/style/style.css | 13 +++++++++---- .../tools/project_manager/project_manager/style.py | 2 +- .../project_manager/project_manager/widgets.py | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 6e1b6e822b..c8adc0674a 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -51,10 +51,9 @@ "border-hover": "rgba(168, 175, 189, .3)", "border-focus": "rgb(92, 173, 214)", - "warning-btn-bg": "rgb(201, 54, 54)", - "warning-btn-bg": "rgb(201, 54, 54)", - "warning-btn-bg-disabled": "rgba(201, 54, 54, 64)", + "delete-btn-bg": "rgb(201, 54, 54)", + "delete-btn-bg-disabled": "rgba(201, 54, 54, 64)", "tab-widget": { "bg": "#21252B", diff --git a/openpype/style/style.css b/openpype/style/style.css index 65e8d0cb40..d9b0ff7421 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -734,11 +734,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: {color:bg-view-hover}; } -#WarningButton { - background: {color:warning-btn-bg}; +#DeleteButton { + background: {color:delete-btn-bg}; } -#WarningButton:disabled { - background: {color:warning-btn-bg-disabled}; +#DeleteButton:disabled { + background: {color:delete-btn-bg-disabled}; } /* Launcher specific stylesheets */ @@ -1228,6 +1228,11 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: #21252B; } +/* Tray */ +#TrayRestartButton { + background: {color:restart-btn-bg}; +} + /* Globally used names */ #Separator { background: {color:bg-menu-separator}; diff --git a/openpype/tools/project_manager/project_manager/style.py b/openpype/tools/project_manager/project_manager/style.py index 980c637bca..9fa7a5520b 100644 --- a/openpype/tools/project_manager/project_manager/style.py +++ b/openpype/tools/project_manager/project_manager/style.py @@ -95,7 +95,7 @@ class ResourceCache: def get_warning_pixmap(cls): src_image = get_warning_image() colors = get_objected_colors() - color_value = colors["warning-btn-bg"] + color_value = colors["delete-btn-bg"] return paint_image_with_color( src_image, diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index e58dcc7d0c..4b5aca35ef 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -369,7 +369,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog): cancel_btn = QtWidgets.QPushButton("Cancel", self) cancel_btn.setToolTip("Cancel deletion of the project") confirm_btn = QtWidgets.QPushButton("Permanently Delete Project", self) - confirm_btn.setObjectName("WarningButton") + confirm_btn.setObjectName("DeleteButton") confirm_btn.setEnabled(False) confirm_btn.setToolTip("Confirm deletion") From a18bdbc418e004bb8d3c0606296d1649872fa74b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Jan 2022 17:53:56 +0100 Subject: [PATCH 41/62] changed dialog and added restart and update action to tray --- openpype/style/data.json | 1 + openpype/tools/tray/images/gifts.png | Bin 0 -> 8605 bytes openpype/tools/tray/pype_tray.py | 118 +++++++++++++++++++++++---- openpype/tools/utils/__init__.py | 6 +- 4 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 openpype/tools/tray/images/gifts.png diff --git a/openpype/style/data.json b/openpype/style/data.json index c8adc0674a..1db0c732cf 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -51,6 +51,7 @@ "border-hover": "rgba(168, 175, 189, .3)", "border-focus": "rgb(92, 173, 214)", + "restart-btn-bg": "#458056", "delete-btn-bg": "rgb(201, 54, 54)", "delete-btn-bg-disabled": "rgba(201, 54, 54, 64)", diff --git a/openpype/tools/tray/images/gifts.png b/openpype/tools/tray/images/gifts.png new file mode 100644 index 0000000000000000000000000000000000000000..57fb3f286312878c641f27362653ede1b7746810 GIT binary patch literal 8605 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 T%VmTC_`iqu)s>1NPv86(+fBse literal 0 HcmV?d00001 diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 7f78140211..0d3e7ae04c 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -29,11 +29,51 @@ from openpype.settings import ( ProjectSettings, DefaultsNotDefined ) -from openpype.tools.utils import WrappedCallbackItem +from openpype.tools.utils import ( + WrappedCallbackItem, + paint_image_with_color +) from .pype_info_widget import PypeInfoWidget +# TODO PixmapLabel should be moved to 'utils' in other future PR so should be +# imported from there +class PixmapLabel(QtWidgets.QLabel): + """Label resizing image to height of font.""" + def __init__(self, pixmap, parent): + super(PixmapLabel, self).__init__(parent) + self._empty_pixmap = QtGui.QPixmap(0, 0) + self._source_pixmap = pixmap + + def set_source_pixmap(self, pixmap): + """Change source image.""" + self._source_pixmap = pixmap + self._set_resized_pix() + + def _get_pix_size(self): + size = self.fontMetrics().height() * 3 + return size, size + + def _set_resized_pix(self): + if self._source_pixmap is None: + self.setPixmap(self._empty_pixmap) + return + width, height = self._get_pix_size() + self.setPixmap( + self._source_pixmap.scaled( + width, + height, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + ) + + def resizeEvent(self, event): + self._set_resized_pix() + super(PixmapLabel, self).resizeEvent(event) + + class VersionDialog(QtWidgets.QDialog): restart_requested = QtCore.Signal() ignore_requested = QtCore.Signal() @@ -43,7 +83,7 @@ class VersionDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(VersionDialog, self).__init__(parent) - self.setWindowTitle("Wrong OpenPype version") + self.setWindowTitle("OpenPype update is needed") icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) self.setWindowIcon(icon) self.setWindowFlags( @@ -54,12 +94,23 @@ class VersionDialog(QtWidgets.QDialog): self.setMinimumWidth(self._min_width) self.setMinimumHeight(self._min_height) - label_widget = QtWidgets.QLabel(self) + top_widget = QtWidgets.QWidget(self) + + gift_pixmap = self._get_gift_pixmap() + gift_icon_label = PixmapLabel(gift_pixmap, top_widget) + + label_widget = QtWidgets.QLabel(top_widget) label_widget.setWordWrap(True) - ignore_btn = QtWidgets.QPushButton("Ignore", self) - ignore_btn.setObjectName("WarningButton") - restart_btn = QtWidgets.QPushButton("Restart and Change", self) + top_layout = QtWidgets.QHBoxLayout(top_widget) + # top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.setSpacing(10) + top_layout.addWidget(gift_icon_label, 0, QtCore.Qt.AlignCenter) + top_layout.addWidget(label_widget, 1) + + ignore_btn = QtWidgets.QPushButton("Later", self) + restart_btn = QtWidgets.QPushButton("Restart && Update", self) + restart_btn.setObjectName("TrayRestartButton") btns_layout = QtWidgets.QHBoxLayout() btns_layout.addStretch(1) @@ -67,7 +118,7 @@ class VersionDialog(QtWidgets.QDialog): btns_layout.addWidget(restart_btn, 0) layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(label_widget, 0) + layout.addWidget(top_widget, 0) layout.addStretch(1) layout.addLayout(btns_layout, 0) @@ -79,6 +130,21 @@ class VersionDialog(QtWidgets.QDialog): self.setStyleSheet(style.load_stylesheet()) + def _get_gift_pixmap(self): + image_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "images", + "gifts.png" + ) + src_image = QtGui.QImage(image_path) + colors = style.get_objected_colors() + color_value = colors["font"] + + return paint_image_with_color( + src_image, + color_value.get_qcolor() + ) + def showEvent(self, event): super().showEvent(event) self._restart_accepted = False @@ -90,8 +156,8 @@ class VersionDialog(QtWidgets.QDialog): def update_versions(self, current_version, expected_version): message = ( - "Your OpenPype version {} does" - " not match to studio version {}" + "Running OpenPype version is {}." + " Your production has been updated to version {}." ).format(str(current_version), str(expected_version)) self._label_widget.setText(message) @@ -113,6 +179,7 @@ class TrayManager: self.tray_widget = tray_widget self.main_window = main_window self.pype_info_widget = None + self._restart_action = None self.log = Logger.get_logger(self.__class__.__name__) @@ -158,7 +225,14 @@ class TrayManager: self.validate_openpype_version() def validate_openpype_version(self): - if is_current_version_studio_latest(): + using_requested = is_current_version_studio_latest() + self._restart_action.setVisible(not using_requested) + if using_requested: + if ( + self._version_dialog is not None + and self._version_dialog.isVisible() + ): + self._version_dialog.close() return if self._version_dialog is None: @@ -170,25 +244,24 @@ class TrayManager: self._outdated_version_ignored ) - if self._version_dialog.isVisible(): - return - expected_version = get_expected_version() current_version = get_openpype_version() self._version_dialog.update_versions( current_version, expected_version ) - self._version_dialog.exec_() + self._version_dialog.show() + self._version_dialog.raise_() + self._version_dialog.activateWindow() def _restart_and_install(self): self.restart() def _outdated_version_ignored(self): self.show_tray_message( - "Outdated OpenPype version", + "OpenPype version is outdated", ( "Please update your OpenPype as soon as possible." - " All you have to do is to restart tray." + " To update, restart OpenPype Tray application." ) ) @@ -341,9 +414,22 @@ class TrayManager: version_action = QtWidgets.QAction(version_string, self.tray_widget) version_action.triggered.connect(self._on_version_action) + + restart_action = QtWidgets.QAction( + "Restart && Update", self.tray_widget + ) + restart_action.triggered.connect(self._on_restart_action) + restart_action.setVisible(False) + self.tray_widget.menu.addAction(version_action) + self.tray_widget.menu.addAction(restart_action) self.tray_widget.menu.addSeparator() + self._restart_action = restart_action + + def _on_restart_action(self): + self.restart() + def restart(self, reset_version=True): """Restart Tray tool. diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index 65025ac358..eb0cb1eef5 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -6,7 +6,10 @@ from .widgets import ( ) from .error_dialog import ErrorMessageBox -from .lib import WrappedCallbackItem +from .lib import ( + WrappedCallbackItem, + paint_image_with_color +) __all__ = ( @@ -18,4 +21,5 @@ __all__ = ( "ErrorMessageBox", "WrappedCallbackItem", + "paint_image_with_color", ) From 3495ed1b06be855a9551fb6312815ce79cadcd18 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 15 Jan 2022 03:44:00 +0000 Subject: [PATCH 42/62] [Automated] Bump version --- CHANGELOG.md | 20 ++++++++++++++------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e92c16dc5f..e7cd3cb7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,26 @@ # Changelog -## [3.8.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.8.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.7.0...HEAD) +### πŸ“– Documentation + +- Slack: Add review to notification message [\#2498](https://github.com/pypeclub/OpenPype/pull/2498) + **πŸ†• New features** - Flame: OpenTimelineIO Export Modul [\#2398](https://github.com/pypeclub/OpenPype/pull/2398) **πŸš€ Enhancements** +- General: Be able to use anatomy data in ffmpeg output arguments [\#2525](https://github.com/pypeclub/OpenPype/pull/2525) +- Expose toggle publish plug-in settings for Maya Look Shading Engine Naming [\#2521](https://github.com/pypeclub/OpenPype/pull/2521) - Photoshop: Move implementation to OpenPype [\#2510](https://github.com/pypeclub/OpenPype/pull/2510) - TimersManager: Move module one hierarchy higher [\#2501](https://github.com/pypeclub/OpenPype/pull/2501) +- Slack: notifications are sent with Openpype logo and bot name [\#2499](https://github.com/pypeclub/OpenPype/pull/2499) - Ftrack: Event handlers settings [\#2496](https://github.com/pypeclub/OpenPype/pull/2496) +- Flame - create publishable clips [\#2495](https://github.com/pypeclub/OpenPype/pull/2495) - Tools: Fix style and modality of errors in loader and creator [\#2489](https://github.com/pypeclub/OpenPype/pull/2489) - Project Manager: Remove project button cleanup [\#2482](https://github.com/pypeclub/OpenPype/pull/2482) - Tools: Be able to change models of tasks and assets widgets [\#2475](https://github.com/pypeclub/OpenPype/pull/2475) @@ -23,23 +31,27 @@ - Fix \#2453 Refactor missing \_get\_reference\_node method [\#2455](https://github.com/pypeclub/OpenPype/pull/2455) - Houdini: Remove broken unique name counter [\#2450](https://github.com/pypeclub/OpenPype/pull/2450) - Maya: Improve lib.polyConstraint performance when Select tool is not the active tool context [\#2447](https://github.com/pypeclub/OpenPype/pull/2447) +- General: Validate third party before build [\#2425](https://github.com/pypeclub/OpenPype/pull/2425) - Maya : add option to not group reference in ReferenceLoader [\#2383](https://github.com/pypeclub/OpenPype/pull/2383) **πŸ› Bug fixes** +- Fix published frame content for sequence starting with 0 [\#2513](https://github.com/pypeclub/OpenPype/pull/2513) +- Fix \#2497: reset empty string attributes correctly to "" instead of "None" [\#2506](https://github.com/pypeclub/OpenPype/pull/2506) - General: Settings work if OpenPypeVersion is available [\#2494](https://github.com/pypeclub/OpenPype/pull/2494) - General: PYTHONPATH may break OpenPype dependencies [\#2493](https://github.com/pypeclub/OpenPype/pull/2493) - Workfiles tool: Files widget show files on first show [\#2488](https://github.com/pypeclub/OpenPype/pull/2488) - General: Custom template paths filter fix [\#2483](https://github.com/pypeclub/OpenPype/pull/2483) - Loader: Remove always on top flag in tray [\#2480](https://github.com/pypeclub/OpenPype/pull/2480) - General: Anatomy does not return root envs as unicode [\#2465](https://github.com/pypeclub/OpenPype/pull/2465) +- Maya: Validate Shape Zero do not keep fixed geometry vertices selected/active after repair [\#2456](https://github.com/pypeclub/OpenPype/pull/2456) **Merged pull requests:** +- Fix create zip tool - path argument [\#2522](https://github.com/pypeclub/OpenPype/pull/2522) - General: Modules import function output fix [\#2492](https://github.com/pypeclub/OpenPype/pull/2492) - AE: fix hiding of alert window below Publish [\#2491](https://github.com/pypeclub/OpenPype/pull/2491) - Maya: Validate NGONs re-use polyConstraint code from openpype.host.maya.api.lib [\#2458](https://github.com/pypeclub/OpenPype/pull/2458) -- Version handling [\#2363](https://github.com/pypeclub/OpenPype/pull/2363) ## [3.7.0](https://github.com/pypeclub/OpenPype/tree/3.7.0) (2022-01-04) @@ -68,7 +80,6 @@ - Blender 3: Support auto install for new blender version [\#2377](https://github.com/pypeclub/OpenPype/pull/2377) - Maya add render image path to settings [\#2375](https://github.com/pypeclub/OpenPype/pull/2375) - Hiero: python3 compatibility [\#2365](https://github.com/pypeclub/OpenPype/pull/2365) -- Maya: Add is\_static\_image\_plane and is\_in\_all\_views option in imagePlaneLoader [\#2356](https://github.com/pypeclub/OpenPype/pull/2356) **πŸ› Bug fixes** @@ -87,8 +98,6 @@ - Nuke: fixing menu re-drawing during context change [\#2374](https://github.com/pypeclub/OpenPype/pull/2374) - Webpublisher: Fix assignment of families of TVpaint instances [\#2373](https://github.com/pypeclub/OpenPype/pull/2373) - Nuke: fixing node name based on switched asset name [\#2369](https://github.com/pypeclub/OpenPype/pull/2369) -- Tools: Placeholder color [\#2359](https://github.com/pypeclub/OpenPype/pull/2359) -- Houdini: Fix HDA creation [\#2350](https://github.com/pypeclub/OpenPype/pull/2350) **Merged pull requests:** @@ -96,7 +105,6 @@ - Maya: Replaced PATH usage with vendored oiio path for maketx utility [\#2405](https://github.com/pypeclub/OpenPype/pull/2405) - \[Fix\]\[MAYA\] Handle message type attribute within CollectLook [\#2394](https://github.com/pypeclub/OpenPype/pull/2394) - Add validator to check correct version of extension for PS and AE [\#2387](https://github.com/pypeclub/OpenPype/pull/2387) -- Linux : flip updating submodules logic [\#2357](https://github.com/pypeclub/OpenPype/pull/2357) ## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23) diff --git a/openpype/version.py b/openpype/version.py index 1f005d6952..520048bca7 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.8.0-nightly.3" +__version__ = "3.8.0-nightly.4" diff --git a/pyproject.toml b/pyproject.toml index f9155f05a3..598d2b4798 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.8.0-nightly.3" # OpenPype +version = "3.8.0-nightly.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From ab97a3266a9bfdb563ae74692656f8c8b86e4f4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Jan 2022 07:05:47 +0000 Subject: [PATCH 43/62] build(deps): bump shelljs from 0.8.4 to 0.8.5 in /website Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5. - [Release notes](https://github.com/shelljs/shelljs/releases) - [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md) - [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5) --- updated-dependencies: - dependency-name: shelljs dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 89da2289de..e34f951572 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2250,9 +2250,9 @@ bail@^1.0.0: integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base16@^1.0.0: version "1.0.0" @@ -4136,9 +4136,9 @@ glob-to-regexp@^0.4.1: integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob@^7.0.0, glob@^7.0.3, glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -4825,6 +4825,13 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-core-module@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -6167,7 +6174,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -7208,7 +7215,16 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2: +resolve@^1.1.6: + version "1.21.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.21.0.tgz#b51adc97f3472e6a5cf4444d34bc9d6b9037591f" + integrity sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA== + dependencies: + is-core-module "^2.8.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^1.14.2, resolve@^1.3.2: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -7533,9 +7549,9 @@ shell-quote@1.7.2: integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== shelljs@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" - integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -7896,6 +7912,11 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" From b432613e726770d4f21360f8db65ff3936af8429 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 16 Jan 2022 16:13:10 +0100 Subject: [PATCH 44/62] moved implementation from avalon to openpype --- openpype/hosts/aftereffects/api/README.md | 66 + openpype/hosts/aftereffects/api/__init__.py | 167 +-- openpype/hosts/aftereffects/api/extension.zxp | Bin 0 -> 100915 bytes .../hosts/aftereffects/api/extension/.debug | 32 + .../api/extension/CSXS/manifest.xml | 79 ++ .../api/extension/css/boilerplate.css | 327 +++++ .../aftereffects/api/extension/css/styles.css | 51 + .../css/topcoat-desktop-dark.min.css | 1 + .../api/extension/icons/iconDarkNormal.png | Bin 0 -> 18659 bytes .../api/extension/icons/iconDarkRollover.png | Bin 0 -> 18663 bytes .../api/extension/icons/iconDisabled.png | Bin 0 -> 18663 bytes .../api/extension/icons/iconNormal.png | Bin 0 -> 18225 bytes .../api/extension/icons/iconRollover.png | Bin 0 -> 18664 bytes .../aftereffects/api/extension/index.html | 136 ++ .../api/extension/js/libs/CSInterface.js | 1193 +++++++++++++++++ .../api/extension/js/libs/jquery-2.0.2.min.js | 6 + .../api/extension/js/libs/json.js | 530 ++++++++ .../api/extension/js/libs/loglevel.min.js | 2 + .../api/extension/js/libs/wsrpc.js | 393 ++++++ .../api/extension/js/libs/wsrpc.min.js | 1 + .../aftereffects/api/extension/js/main.js | 347 +++++ .../api/extension/js/themeManager.js | 128 ++ .../api/extension/jsx/hostscript.jsx | 723 ++++++++++ .../hosts/aftereffects/api/launch_logic.py | 319 +++++ openpype/hosts/aftereffects/api/lib.py | 71 + openpype/hosts/aftereffects/api/panel.PNG | Bin 0 -> 8756 bytes .../hosts/aftereffects/api/panel_failure.PNG | Bin 0 -> 13568 bytes openpype/hosts/aftereffects/api/pipeline.py | 272 ++++ openpype/hosts/aftereffects/api/plugin.py | 46 + openpype/hosts/aftereffects/api/workio.py | 49 + openpype/hosts/aftereffects/api/ws_stub.py | 605 +++++++++ .../plugins/create/create_local_render.py | 4 - .../plugins/create/create_render.py | 42 +- .../plugins/load/load_background.py | 22 +- .../aftereffects/plugins/load/load_file.py | 19 +- .../plugins/publish/add_publish_highlight.py | 4 +- .../aftereffects/plugins/publish/closeAE.py | 4 +- .../plugins/publish/collect_audio.py | 5 +- .../plugins/publish/collect_current_file.py | 4 +- .../publish/collect_extension_version.py | 10 +- .../plugins/publish/collect_render.py | 6 +- .../plugins/publish/extract_local_render.py | 9 +- .../plugins/publish/extract_save_scene.py | 4 +- .../plugins/publish/increment_workfile.py | 4 +- .../publish/remove_publish_highlight.py | 4 +- .../publish/validate_instance_asset.py | 4 +- .../publish/validate_scene_settings.py | 10 +- openpype/scripts/non_python_host_launch.py | 2 +- 48 files changed, 5523 insertions(+), 178 deletions(-) create mode 100644 openpype/hosts/aftereffects/api/README.md create mode 100644 openpype/hosts/aftereffects/api/extension.zxp create mode 100644 openpype/hosts/aftereffects/api/extension/.debug create mode 100644 openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml create mode 100644 openpype/hosts/aftereffects/api/extension/css/boilerplate.css create mode 100644 openpype/hosts/aftereffects/api/extension/css/styles.css create mode 100644 openpype/hosts/aftereffects/api/extension/css/topcoat-desktop-dark.min.css create mode 100644 openpype/hosts/aftereffects/api/extension/icons/iconDarkNormal.png create mode 100644 openpype/hosts/aftereffects/api/extension/icons/iconDarkRollover.png create mode 100644 openpype/hosts/aftereffects/api/extension/icons/iconDisabled.png create mode 100644 openpype/hosts/aftereffects/api/extension/icons/iconNormal.png create mode 100644 openpype/hosts/aftereffects/api/extension/icons/iconRollover.png create mode 100644 openpype/hosts/aftereffects/api/extension/index.html create mode 100644 openpype/hosts/aftereffects/api/extension/js/libs/CSInterface.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/libs/jquery-2.0.2.min.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/libs/json.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/libs/loglevel.min.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/libs/wsrpc.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/libs/wsrpc.min.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/main.js create mode 100644 openpype/hosts/aftereffects/api/extension/js/themeManager.js create mode 100644 openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx create mode 100644 openpype/hosts/aftereffects/api/launch_logic.py create mode 100644 openpype/hosts/aftereffects/api/lib.py create mode 100644 openpype/hosts/aftereffects/api/panel.PNG create mode 100644 openpype/hosts/aftereffects/api/panel_failure.PNG create mode 100644 openpype/hosts/aftereffects/api/pipeline.py create mode 100644 openpype/hosts/aftereffects/api/plugin.py create mode 100644 openpype/hosts/aftereffects/api/workio.py create mode 100644 openpype/hosts/aftereffects/api/ws_stub.py diff --git a/openpype/hosts/aftereffects/api/README.md b/openpype/hosts/aftereffects/api/README.md new file mode 100644 index 0000000000..667324f7a4 --- /dev/null +++ b/openpype/hosts/aftereffects/api/README.md @@ -0,0 +1,66 @@ +# Photoshop Integration + +Requirements: This extension requires use of Javascript engine, which is +available since CC 16.0. +Please check your File>Project Settings>Expressions>Expressions Engine + +## Setup + +The After Effects integration requires two components to work; `extension` and `server`. + +### Extension + +To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd). + +``` +ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp +``` +OR +download [Anastasiy’s Extension Manager](https://install.anastasiy.com/) + +### Server + +The easiest way to get the server and After Effects launch is with: + +``` +python -c ^"import avalon.photoshop;avalon.aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" +``` + +`avalon.aftereffects.launch` launches the application and server, and also closes the server when After Effects exists. + +## Usage + +The After Effects extension can be found under `Window > Extensions > OpenPype`. Once launched you should be presented with a panel like this: + +![Avalon Panel](panel.PNG "Avalon Panel") + + +## Developing + +### Extension +When developing the extension you can load it [unsigned](https://github.com/Adobe-CEP/CEP-Resources/blob/master/CEP_9.x/Documentation/CEP%209.0%20HTML%20Extension%20Cookbook.md#debugging-unsigned-extensions). + +When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). + +``` +ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-After-Effects avalon extension.p12 +ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to avalon-core}\avalon\aftereffects\extension.zxp extension.p12 avalon +``` + +### Plugin Examples + +These plugins were made with the [polly config](https://github.com/mindbender-studio/config). To fully integrate and load, you will have to use this config and add `image` to the [integration plugin](https://github.com/mindbender-studio/config/blob/master/polly/plugins/publish/integrate_asset.py). + +Expected deployed extension location on default Windows: +`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\com.openpype.AE.panel` + +For easier debugging of Javascript: +https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1 +Add (optional) --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome +then localhost:8092 + +Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 +## Resources + - https://javascript-tools-guide.readthedocs.io/introduction/index.html + - https://github.com/Adobe-CEP/Getting-Started-guides + - https://github.com/Adobe-CEP/CEP-Resources diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index b1edb91a5c..a7bbd8e604 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -1,115 +1,66 @@ -import os -import sys -import logging +"""Public API -from avalon import io -from avalon import api as avalon -from Qt import QtWidgets -from openpype import lib, api -import pyblish.api as pyblish -import openpype.hosts.aftereffects +Anything that isn't defined here is INTERNAL and unreliable for external use. + +""" + +from .launch_logic import ( + get_stub, + stub, +) + +from .pipeline import ( + ls, + Creator, + install, + list_instances, + remove_instance, + containerise +) + +from .workio import ( + file_extensions, + has_unsaved_changes, + save_file, + open_file, + current_file, + work_root, +) + +from .lib import ( + maintained_selection, + get_extension_manifest_path +) + +from .plugin import ( + AfterEffectsLoader +) -log = logging.getLogger("openpype.hosts.aftereffects") +__all__ = [ + # launch_logic + "get_stub", + "stub", + # pipeline + "ls", + "Creator", + "install", + "list_instances", + "remove_instance", + "containerise", -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.aftereffects.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + "file_extensions", + "has_unsaved_changes", + "save_file", + "open_file", + "current_file", + "work_root", + # lib + "maintained_selection", + "get_extension_manifest_path", -def check_inventory(): - if not lib.any_outdated(): - return - - host = pyblish.registered_host() - outdated_containers = [] - for container in host.ls(): - representation = container['representation'] - representation_doc = io.find_one( - { - "_id": io.ObjectId(representation), - "type": "representation" - }, - projection={"parent": True} - ) - if representation_doc and not lib.is_latest(representation_doc): - outdated_containers.append(container) - - # Warn about outdated containers. - print("Starting new QApplication..") - app = QtWidgets.QApplication(sys.argv) - - message_box = QtWidgets.QMessageBox() - message_box.setIcon(QtWidgets.QMessageBox.Warning) - msg = "There are outdated containers in the scene." - message_box.setText(msg) - message_box.exec_() - - # Garbage collect QApplication. - del app - - -def application_launch(): - check_inventory() - - -def install(): - print("Installing Pype config...") - - pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(avalon.Creator, CREATE_PATH) - log.info(PUBLISH_PATH) - - pyblish.register_callback( - "instanceToggled", on_pyblish_instance_toggled - ) - - avalon.on("application.launched", application_launch) - - -def uninstall(): - pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) - - -def on_pyblish_instance_toggled(instance, old_value, new_value): - """Toggle layer visibility on instance toggles.""" - instance[0].Visible = new_value - - -def get_asset_settings(): - """Get settings on current asset from database. - - Returns: - dict: Scene data. - - """ - asset_data = lib.get_asset()["data"] - fps = asset_data.get("fps") - frame_start = asset_data.get("frameStart") - frame_end = asset_data.get("frameEnd") - handle_start = asset_data.get("handleStart") - handle_end = asset_data.get("handleEnd") - resolution_width = asset_data.get("resolutionWidth") - resolution_height = asset_data.get("resolutionHeight") - duration = (frame_end - frame_start + 1) + handle_start + handle_end - entity_type = asset_data.get("entityType") - - scene_data = { - "fps": fps, - "frameStart": frame_start, - "frameEnd": frame_end, - "handleStart": handle_start, - "handleEnd": handle_end, - "resolutionWidth": resolution_width, - "resolutionHeight": resolution_height, - "duration": duration - } - - return scene_data + # plugin + "AfterEffectsLoader", +] diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp new file mode 100644 index 0000000000000000000000000000000000000000..35b0c0fc42a731150521187312ef21243882f300 GIT binary patch literal 100915 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

CHAj%#mo5y_a_ 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�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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml new file mode 100644 index 0000000000..2480089825 --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ./index.html + ./jsx/hostscript.jsx + + + true + + + Panel +

OpenPype + + + 200 + 100 + + + + + + ./icons/iconNormal.png + ./icons/iconRollover.png + ./icons/iconDisabled.png + ./icons/iconDarkNormal.png + ./icons/iconDarkRollover.png + + + + + + \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/css/boilerplate.css b/openpype/hosts/aftereffects/api/extension/css/boilerplate.css new file mode 100644 index 0000000000..d208999b8a --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/css/boilerplate.css @@ -0,0 +1,327 @@ +/* + * HTML5 ✰ Boilerplate + * + * What follows is the result of much research on cross-browser styling. + * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, + * Kroc Camen, and the H5BP dev community and team. + * + * Detailed information about this CSS: h5bp.com/css + * + * ==|== normalize ========================================================== + */ + + +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; } +[hidden] { display: none; } + + +/* ============================================================================= + Base + ========================================================================== */ + +/* + * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units + * 2. Force vertical scrollbar in non-IE + * 3. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g + */ + +html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + +body { margin: 0; font-size: 100%; line-height: 1.231; } + +body, button, input, select, textarea { font-family: helvetica, arial,"lucida grande", verdana, "パむγƒͺγ‚ͺ", "οΌ­οΌ³ Pゴシック", sans-serif; color: #222; } +/* + * Remove text-shadow in selection highlight: h5bp.com/i + * These selection declarations have to be separate + * Also: hot pink! (or customize the background color to match your design) + */ + +::selection { text-shadow: none; background-color: highlight; color: highlighttext; } + + +/* ============================================================================= + Links + ========================================================================== */ + +a { color: #00e; } +a:visited { color: #551a8b; } +a:hover { color: #06e; } +a:focus { outline: thin dotted; } + +/* Improve readability when focused and hovered in all browsers: h5bp.com/h */ +a:hover, a:active { outline: 0; } + + +/* ============================================================================= + Typography + ========================================================================== */ + +abbr[title] { border-bottom: 1px dotted; } + +b, strong { font-weight: bold; } + +blockquote { margin: 1em 40px; } + +dfn { font-style: italic; } + +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } + +ins { background: #ff9; color: #000; text-decoration: none; } + +mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } + +/* Redeclare monospace font family: h5bp.com/j */ +pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; } + +/* Improve readability of pre-formatted text in all browsers */ +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } + +q { quotes: none; } +q:before, q:after { content: ""; content: none; } + +small { font-size: 85%; } + +/* Position subscript and superscript content without affecting line-height: h5bp.com/k */ +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + + +/* ============================================================================= + Lists + ========================================================================== */ + +ul, ol { margin: 1em 0; padding: 0 0 0 40px; } +dd { margin: 0 0 0 40px; } +nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } + + +/* ============================================================================= + Embedded content + ========================================================================== */ + +/* + * 1. Improve image quality when scaled in IE7: h5bp.com/d + * 2. Remove the gap between images and borders on image containers: h5bp.com/e + */ + +img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } + +/* + * Correct overflow not hidden in IE9 + */ + +svg:not(:root) { overflow: hidden; } + + +/* ============================================================================= + Figures + ========================================================================== */ + +figure { margin: 0; } + + +/* ============================================================================= + Forms + ========================================================================== */ + +form { margin: 0; } +fieldset { border: 0; margin: 0; padding: 0; } + +/* Indicate that 'label' will shift focus to the associated form element */ +label { cursor: pointer; } + +/* + * 1. Correct color not inheriting in IE6/7/8/9 + * 2. Correct alignment displayed oddly in IE6/7 + */ + +legend { border: 0; *margin-left: -7px; padding: 0; } + +/* + * 1. Correct font-size not inheriting in all browsers + * 2. Remove margins in FF3/4 S5 Chrome + * 3. Define consistent vertical alignment display in all browsers + */ + +button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } + +/* + * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet) + */ + +button, input { line-height: normal; } + +/* + * 1. Display hand cursor for clickable form elements + * 2. Allow styling of clickable form elements in iOS + * 3. Correct inner spacing displayed oddly in IE7 (doesn't effect IE6) + */ + +button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } + +/* + * Consistent box sizing and appearance + */ + +input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; } +input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } +input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } + +/* + * Remove inner padding and border in FF3/4: h5bp.com/l + */ + +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } + +/* + * 1. Remove default vertical scrollbar in IE6/7/8/9 + * 2. Allow only vertical resizing + */ + +textarea { overflow: auto; vertical-align: top; resize: vertical; } + +/* Colors for form validity */ +input:valid, textarea:valid { } +input:invalid, textarea:invalid { background-color: #f0dddd; } + + +/* ============================================================================= + Tables + ========================================================================== */ + +table { border-collapse: collapse; border-spacing: 0; } +td { vertical-align: top; } + + +/* ==|== primary styles ===================================================== + Author: + ========================================================================== */ + +/* ==|== media queries ====================================================== + PLACEHOLDER Media Queries for Responsive Design. + These override the primary ('mobile first') styles + Modify as content requires. + ========================================================================== */ + +@media only screen and (min-width: 480px) { + /* Style adjustments for viewports 480px and over go here */ + +} + +@media only screen and (min-width: 768px) { + /* Style adjustments for viewports 768px and over go here */ + +} + + + +/* ==|== non-semantic helper classes ======================================== + Please define your styles before this section. + ========================================================================== */ + +/* For image replacement */ +.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; } +.ir br { display: none; } + +/* Hide from both screenreaders and browsers: h5bp.com/u */ +.hidden { display: none !important; visibility: hidden; } + +/* Hide only visually, but have it available for screenreaders: h5bp.com/v */ +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + +/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */ +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* Contain floats: h5bp.com/q */ +.clearfix:before, .clearfix:after { content: ""; display: table; } +.clearfix:after { clear: both; } +.clearfix { *zoom: 1; } + + + +/* ==|== print styles ======================================================= + Print styles. + Inlined to avoid required HTTP connection: h5bp.com/r + ========================================================================== */ + +@media print { + * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */ + a, a:visited { text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + table { display: table-header-group; } /* h5bp.com/t */ + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3 { page-break-after: avoid; } +} + +/* reflow reset for -webkit-margin-before: 1em */ +p { margin: 0; } + +html { + overflow-y: auto; + background-color: transparent; + height: 100%; +} + +body { + background: #fff; + font: normal 100%; + position: relative; + height: 100%; +} + +body, div, img, p, button, input, select, textarea { + box-sizing: border-box; +} + +.image { + display: block; +} + +input { + cursor: default; + display: block; +} + +input[type=button] { + background-color: #e5e9e8; + border: 1px solid #9daca9; + border-radius: 4px; + box-shadow: inset 0 1px #fff; + font: inherit; + letter-spacing: inherit; + text-indent: inherit; + color: inherit; +} + +input[type=button]:hover { + background-color: #eff1f1; +} + +input[type=button]:active { + background-color: #d2d6d6; + border: 1px solid #9daca9; + box-shadow: inset 0 1px rgba(0,0,0,0.1); +} + +/* Reset anchor styles to an unstyled default to be in parity with design surface. It + is presumed that most link styles in real-world designs are custom (non-default). */ +a, a:visited, a:hover, a:active { + color: inherit; + text-decoration: inherit; +} \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/css/styles.css b/openpype/hosts/aftereffects/api/extension/css/styles.css new file mode 100644 index 0000000000..c9cf2b93ac --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/css/styles.css @@ -0,0 +1,51 @@ +/*Your styles*/ + + body { + margin: 10px; +} + + +#content { + margin-right:auto; + margin-left:auto; + vertical-align:middle; + width:100%; +} + + +#btn_test{ + width: 100%; +} + + + + +/* +Those classes will be edited at runtime with values specified +by the settings of the CC application +*/ +.hostFontColor{} +.hostFontFamily{} +.hostFontSize{} + +/*font family, color and size*/ +.hostFont{} +/*background color*/ +.hostBgd{} +/*lighter background color*/ +.hostBgdLight{} +/*darker background color*/ +.hostBgdDark{} +/*background color and font*/ +.hostElt{} + + +.hostButton{ + border:1px solid; + border-radius:2px; + height:20px; + vertical-align:bottom; + font-family:inherit; + color:inherit; + font-size:inherit; +} \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/css/topcoat-desktop-dark.min.css b/openpype/hosts/aftereffects/api/extension/css/topcoat-desktop-dark.min.css new file mode 100644 index 0000000000..6b479def43 --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/css/topcoat-desktop-dark.min.css @@ -0,0 +1 @@ +.button-bar{display:table;table-layout:fixed;white-space:nowrap;margin:0;padding:0}.button-bar__item{display:table-cell;width:auto;border-radius:0}.button-bar__item>input{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.button-bar__button{border-radius:inherit}.button-bar__item:disabled{opacity:.3;cursor:default;pointer-events:none}.button,.topcoat-button,.topcoat-button--quiet,.topcoat-button--large,.topcoat-button--large--quiet,.topcoat-button--cta,.topcoat-button--large--cta,.topcoat-button-bar__button,.topcoat-button-bar__button--large{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.button--disabled,.topcoat-button:disabled,.topcoat-button--quiet:disabled,.topcoat-button--large:disabled,.topcoat-button--large--quiet:disabled,.topcoat-button--cta:disabled,.topcoat-button--large--cta:disabled,.topcoat-button-bar__button:disabled,.topcoat-button-bar__button--large:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-button,.topcoat-button--quiet,.topcoat-button--large,.topcoat-button--large--quiet,.topcoat-button--cta,.topcoat-button--large--cta,.topcoat-button-bar__button,.topcoat-button-bar__button--large{padding:0 .563rem;font-size:12px;line-height:1.313rem;letter-spacing:0;color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);vertical-align:top;background-color:#595b5b;box-shadow:inset 0 1px #737373;border:1px solid #333434;border-radius:4px}.topcoat-button:hover,.topcoat-button--quiet:hover,.topcoat-button--large:hover,.topcoat-button--large--quiet:hover,.topcoat-button-bar__button:hover,.topcoat-button-bar__button--large:hover{background-color:#626465}.topcoat-button:focus,.topcoat-button--quiet:focus,.topcoat-button--quiet:hover:focus,.topcoat-button--large:focus,.topcoat-button--large--quiet:focus,.topcoat-button--large--quiet:hover:focus,.topcoat-button--cta:focus,.topcoat-button--large--cta:focus,.topcoat-button-bar__button:focus,.topcoat-button-bar__button--large:focus{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1;outline:0}.topcoat-button:active,.topcoat-button--large:active,.topcoat-button-bar__button:active,.topcoat-button-bar__button--large:active,:checked+.topcoat-button-bar__button{border:1px solid #333434;background-color:#3f4041;box-shadow:inset 0 1px rgba(0,0,0,.05)}.topcoat-button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.topcoat-button--quiet:hover,.topcoat-button--large--quiet:hover{text-shadow:0 -1px rgba(0,0,0,.69);border:1px solid #333434;box-shadow:inset 0 1px #737373}.topcoat-button--quiet:active,.topcoat-button--quiet:focus:active,.topcoat-button--large--quiet:active,.topcoat-button--large--quiet:focus:active{color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);background-color:#3f4041;border:1px solid #333434;box-shadow:inset 0 1px rgba(0,0,0,.05)}.topcoat-button--large,.topcoat-button--large--quiet,.topcoat-button-bar__button--large{font-size:.875rem;font-weight:600;line-height:1.688rem;padding:0 .875rem}.topcoat-button--large--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.topcoat-button--cta,.topcoat-button--large--cta{border:1px solid #134f7f;background-color:#288edf;box-shadow:inset 0 1px rgba(255,255,255,.36);color:#fff;font-weight:500;text-shadow:0 -1px rgba(0,0,0,.36)}.topcoat-button--cta:hover,.topcoat-button--large--cta:hover{background-color:#4ca1e4}.topcoat-button--cta:active,.topcoat-button--large--cta:active{background-color:#1e7dc8;box-shadow:inset 0 1px rgba(0,0,0,.12)}.topcoat-button--large--cta{font-size:.875rem;font-weight:600;line-height:1.688rem;padding:0 .875rem}.button-bar,.topcoat-button-bar{display:table;table-layout:fixed;white-space:nowrap;margin:0;padding:0}.button-bar__item,.topcoat-button-bar__item{display:table-cell;width:auto;border-radius:0}.button-bar__item>input,.topcoat-button-bar__item>input{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.button-bar__button{border-radius:inherit}.button-bar__item:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-button-bar>.topcoat-button-bar__item:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.topcoat-button-bar>.topcoat-button-bar__item:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.topcoat-button-bar__item:first-child>.topcoat-button-bar__button,.topcoat-button-bar__item:first-child>.topcoat-button-bar__button--large{border-right:0}.topcoat-button-bar__item:last-child>.topcoat-button-bar__button,.topcoat-button-bar__item:last-child>.topcoat-button-bar__button--large{border-left:0}.topcoat-button-bar__button{border-radius:inherit}.topcoat-button-bar__button:focus,.topcoat-button-bar__button--large:focus{z-index:1}.topcoat-button-bar__button--large{border-radius:inherit}.button{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.button--disabled{opacity:.3;cursor:default;pointer-events:none}.button,.topcoat-button,.topcoat-button--quiet,.topcoat-button--large,.topcoat-button--large--quiet,.topcoat-button--cta,.topcoat-button--large--cta{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.button--disabled,.topcoat-button:disabled,.topcoat-button--quiet:disabled,.topcoat-button--large:disabled,.topcoat-button--large--quiet:disabled,.topcoat-button--cta:disabled,.topcoat-button--large--cta:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-button,.topcoat-button--quiet,.topcoat-button--large,.topcoat-button--large--quiet,.topcoat-button--cta,.topcoat-button--large--cta{padding:0 .563rem;font-size:12px;line-height:1.313rem;letter-spacing:0;color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);vertical-align:top;background-color:#595b5b;box-shadow:inset 0 1px #737373;border:1px solid #333434;border-radius:4px}.topcoat-button:hover,.topcoat-button--quiet:hover,.topcoat-button--large:hover,.topcoat-button--large--quiet:hover{background-color:#626465}.topcoat-button:focus,.topcoat-button--quiet:focus,.topcoat-button--quiet:hover:focus,.topcoat-button--large:focus,.topcoat-button--large--quiet:focus,.topcoat-button--large--quiet:hover:focus,.topcoat-button--cta:focus,.topcoat-button--large--cta:focus{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1;outline:0}.topcoat-button:active,.topcoat-button--large:active{border:1px solid #333434;background-color:#3f4041;box-shadow:inset 0 1px rgba(0,0,0,.05)}.topcoat-button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.topcoat-button--quiet:hover,.topcoat-button--large--quiet:hover{text-shadow:0 -1px rgba(0,0,0,.69);border:1px solid #333434;box-shadow:inset 0 1px #737373}.topcoat-button--quiet:active,.topcoat-button--quiet:focus:active,.topcoat-button--large--quiet:active,.topcoat-button--large--quiet:focus:active{color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);background-color:#3f4041;border:1px solid #333434;box-shadow:inset 0 1px rgba(0,0,0,.05)}.topcoat-button--large,.topcoat-button--large--quiet{font-size:.875rem;font-weight:600;line-height:1.688rem;padding:0 .875rem}.topcoat-button--large--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.topcoat-button--cta,.topcoat-button--large--cta{border:1px solid #134f7f;background-color:#288edf;box-shadow:inset 0 1px rgba(255,255,255,.36);color:#fff;font-weight:500;text-shadow:0 -1px rgba(0,0,0,.36)}.topcoat-button--cta:hover,.topcoat-button--large--cta:hover{background-color:#4ca1e4}.topcoat-button--cta:active,.topcoat-button--large--cta:active{background-color:#1e7dc8;box-shadow:inset 0 1px rgba(0,0,0,.12)}.topcoat-button--large--cta{font-size:.875rem;font-weight:600;line-height:1.688rem;padding:0 .875rem}input[type=checkbox]{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.checkbox{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox__label{position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox--disabled{opacity:.3;cursor:default;pointer-events:none}.checkbox:before,.checkbox:after{content:'';position:absolute}.checkbox:before{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}input[type=checkbox]{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.checkbox,.topcoat-checkbox__checkmark{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox__label,.topcoat-checkbox{position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox--disabled,input[type=checkbox]:disabled+.topcoat-checkbox__checkmark{opacity:.3;cursor:default;pointer-events:none}.checkbox:before,.checkbox:after,.topcoat-checkbox__checkmark:before,.topcoat-checkbox__checkmark:after{content:'';position:absolute}.checkbox:before,.topcoat-checkbox__checkmark:before{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.topcoat-checkbox__checkmark{height:1rem}input[type=checkbox]{height:1rem;width:1rem;margin-top:0;margin-right:-1rem;margin-bottom:-1rem;margin-left:0}input[type=checkbox]:checked+.topcoat-checkbox__checkmark:after{opacity:1}.topcoat-checkbox{line-height:1rem}.topcoat-checkbox__checkmark:before{width:1rem;height:1rem;background:#595b5b;border:1px solid #333434;border-radius:3px;box-shadow:inset 0 1px #737373}.topcoat-checkbox__checkmark{width:1rem;height:1rem}.topcoat-checkbox__checkmark:after{top:2px;left:1px;opacity:0;width:14px;height:4px;background:transparent;border:7px solid #c6c8c8;border-width:3px;border-top:0;border-right:0;border-radius:1px;-webkit-transform:rotate(-50deg);-ms-transform:rotate(-50deg);transform:rotate(-50deg)}input[type=checkbox]:focus+.topcoat-checkbox__checkmark:before{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1}input[type=checkbox]:active+.topcoat-checkbox__checkmark:before{border:1px solid #333434;background-color:#3f4041;box-shadow:inset 0 1px rgba(0,0,0,.05)}input[type=checkbox]:disabled:active+.topcoat-checkbox__checkmark:before{border:1px solid #333434;background:#595b5b;box-shadow:inset 0 1px #737373}.button,.topcoat-icon-button,.topcoat-icon-button--quiet,.topcoat-icon-button--large,.topcoat-icon-button--large--quiet{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.button--disabled,.topcoat-icon-button:disabled,.topcoat-icon-button--quiet:disabled,.topcoat-icon-button--large:disabled,.topcoat-icon-button--large--quiet:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-icon-button,.topcoat-icon-button--quiet,.topcoat-icon-button--large,.topcoat-icon-button--large--quiet{padding:0 .25rem;line-height:1.313rem;letter-spacing:0;color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);vertical-align:baseline;background-color:#595b5b;box-shadow:inset 0 1px #737373;border:1px solid #333434;border-radius:4px}.topcoat-icon-button:hover,.topcoat-icon-button--quiet:hover,.topcoat-icon-button--large:hover,.topcoat-icon-button--large--quiet:hover{background-color:#626465}.topcoat-icon-button:focus,.topcoat-icon-button--quiet:focus,.topcoat-icon-button--quiet:hover:focus,.topcoat-icon-button--large:focus,.topcoat-icon-button--large--quiet:focus,.topcoat-icon-button--large--quiet:hover:focus{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1;outline:0}.topcoat-icon-button:active,.topcoat-icon-button--large:active{border:1px solid #333434;background-color:#3f4041;box-shadow:inset 0 1px rgba(0,0,0,.05)}.topcoat-icon-button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.topcoat-icon-button--quiet:hover,.topcoat-icon-button--large--quiet:hover{text-shadow:0 -1px rgba(0,0,0,.69);border:1px solid #333434;box-shadow:inset 0 1px #737373}.topcoat-icon-button--quiet:active,.topcoat-icon-button--quiet:focus:active,.topcoat-icon-button--large--quiet:active,.topcoat-icon-button--large--quiet:focus:active{color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);background-color:#3f4041;border:1px solid #333434;box-shadow:inset 0 1px rgba(0,0,0,.05)}.topcoat-icon-button--large,.topcoat-icon-button--large--quiet{width:1.688rem;height:1.688rem;line-height:1.688rem}.topcoat-icon-button--large--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.topcoat-icon,.topcoat-icon--large{position:relative;display:inline-block;vertical-align:top;overflow:hidden;width:.81406rem;height:.81406rem;vertical-align:middle;top:-1px}.topcoat-icon--large{width:1.06344rem;height:1.06344rem;top:-2px}.input{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;vertical-align:top;outline:0}.input:disabled{opacity:.3;cursor:default;pointer-events:none}.list{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:auto;-webkit-overflow-scrolling:touch}.list__header{margin:0}.list__container{padding:0;margin:0;list-style-type:none}.list__item{margin:0;padding:0}.navigation-bar{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;white-space:nowrap;overflow:hidden;word-spacing:0;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navigation-bar__item{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;position:relative;display:inline-block;vertical-align:top;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0}.navigation-bar__title{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.notification{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.notification,.topcoat-notification{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.topcoat-notification{padding:.15em .5em .2em;border-radius:2px;background-color:#ec514e;color:#fff}input[type=radio]{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.radio-button{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.radio-button__label{position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.radio-button:before,.radio-button:after{content:'';position:absolute;border-radius:100%}.radio-button:after{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.radio-button:before{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.radio-button--disabled{opacity:.3;cursor:default;pointer-events:none}input[type=radio]{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.radio-button,.topcoat-radio-button__checkmark{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.radio-button__label,.topcoat-radio-button{position:relative;display:inline-block;vertical-align:top;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.radio-button:before,.radio-button:after,.topcoat-radio-button__checkmark:before,.topcoat-radio-button__checkmark:after{content:'';position:absolute;border-radius:100%}.radio-button:after,.topcoat-radio-button__checkmark:after{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.radio-button:before,.topcoat-radio-button__checkmark:before{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.radio-button--disabled,input[type=radio]:disabled+.topcoat-radio-button__checkmark{opacity:.3;cursor:default;pointer-events:none}input[type=radio]{height:1.063rem;width:1.063rem;margin-top:0;margin-right:-1.063rem;margin-bottom:-1.063rem;margin-left:0}input[type=radio]:checked+.topcoat-radio-button__checkmark:after{opacity:1}.topcoat-radio-button{color:#c6c8c8;line-height:1.063rem}.topcoat-radio-button__checkmark:before{width:1.063rem;height:1.063rem;background:#595b5b;border:1px solid #333434;box-shadow:inset 0 1px #737373}.topcoat-radio-button__checkmark{position:relative;width:1.063rem;height:1.063rem}.topcoat-radio-button__checkmark:after{opacity:0;width:.313rem;height:.313rem;background:#c6c8c8;border:1px solid rgba(0,0,0,.05);box-shadow:0 1px rgba(255,255,255,.1);-webkit-transform:none;-ms-transform:none;transform:none;top:.313rem;left:.313rem}input[type=radio]:focus+.topcoat-radio-button__checkmark:before{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1}input[type=radio]:active+.topcoat-radio-button__checkmark:before{border:1px solid #333434;background-color:#3f4041;box-shadow:inset 0 1px rgba(0,0,0,.05)}input[type=radio]:disabled:active+.topcoat-radio-button__checkmark:before{border:1px solid #333434;background:#595b5b;box-shadow:inset 0 1px #737373}.range{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;vertical-align:top;outline:0;-webkit-appearance:none}.range__thumb{cursor:pointer}.range__thumb--webkit{cursor:pointer;-webkit-appearance:none}.range:disabled{opacity:.3;cursor:default;pointer-events:none}.range,.topcoat-range{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;vertical-align:top;outline:0;-webkit-appearance:none}.range__thumb,.topcoat-range::-moz-range-thumb{cursor:pointer}.range__thumb--webkit,.topcoat-range::-webkit-slider-thumb{cursor:pointer;-webkit-appearance:none}.range:disabled,.topcoat-range:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-range{border-radius:4px;border:1px solid #333434;background-color:#454646;height:.5rem;border-radius:15px}.topcoat-range::-moz-range-track{border-radius:4px;border:1px solid #333434;background-color:#454646;height:.5rem;border-radius:15px}.topcoat-range::-webkit-slider-thumb{height:1.313rem;width:.75rem;background-color:#595b5b;border:1px solid #333434;border-radius:4px;box-shadow:inset 0 1px #737373}.topcoat-range::-moz-range-thumb{height:1.313rem;width:.75rem;background-color:#595b5b;border:1px solid #333434;border-radius:4px;box-shadow:inset 0 1px #737373}.topcoat-range:focus::-webkit-slider-thumb{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1}.topcoat-range:focus::-moz-range-thumb{border:1px solid #0036ff;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1}.topcoat-range:active::-webkit-slider-thumb{border:1px solid #333434;box-shadow:inset 0 1px #737373}.topcoat-range:active::-moz-range-thumb{border:1px solid #333434;box-shadow:inset 0 1px #737373}.search-input{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;vertical-align:top;outline:0;-webkit-appearance:none}input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none}.search-input:disabled{opacity:.3;cursor:default;pointer-events:none}.search-input,.topcoat-search-input,.topcoat-search-input--large{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;vertical-align:top;outline:0;-webkit-appearance:none}input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none}.search-input:disabled,.topcoat-search-input:disabled,.topcoat-search-input--large:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-search-input,.topcoat-search-input--large{line-height:1.313rem;height:1.313rem;font-size:12px;border:1px solid #333434;background-color:#454646;box-shadow:inset 0 1px 0 rgba(0,0,0,.23);color:#c6c8c8;padding:0 0 0 1.3rem;border-radius:15px;background-image:url(../img/search.svg);background-position:1rem center;background-repeat:no-repeat;background-size:12px}.topcoat-search-input:focus,.topcoat-search-input--large:focus{background-color:#595b5b;color:#fff;border:1px solid #0036ff;box-shadow:inset 0 1px 0 rgba(0,0,0,.23),0 0 0 2px #6fb5f1}.topcoat-search-input::-webkit-search-cancel-button,.topcoat-search-input::-webkit-search-decoration,.topcoat-search-input--large::-webkit-search-cancel-button,.topcoat-search-input--large::-webkit-search-decoration{margin-right:5px}.topcoat-search-input:focus::-webkit-input-placeholder,.topcoat-search-input:focus::-webkit-input-placeholder{color:#c6c8c8}.topcoat-search-input:disabled::-webkit-input-placeholder{color:#fff}.topcoat-search-input:disabled::-moz-placeholder{color:#fff}.topcoat-search-input:disabled:-ms-input-placeholder{color:#fff}.topcoat-search-input--large{line-height:1.688rem;height:1.688rem;font-size:.875rem;font-weight:400;padding:0 0 0 1.8rem;border-radius:25px;background-position:1.2rem center;background-size:.875rem}.topcoat-search-input--large:disabled{color:#fff}.topcoat-search-input--large:disabled::-webkit-input-placeholder{color:#fff}.topcoat-search-input--large:disabled::-moz-placeholder{color:#fff}.topcoat-search-input--large:disabled:-ms-input-placeholder{color:#fff}.switch{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.switch__input{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.switch__toggle{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch__toggle:before,.switch__toggle:after{content:'';position:absolute;z-index:-1;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.switch--disabled{opacity:.3;cursor:default;pointer-events:none}.switch,.topcoat-switch{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.switch__input,.topcoat-switch__input{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.switch__toggle,.topcoat-switch__toggle{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch__toggle:before,.switch__toggle:after,.topcoat-switch__toggle:before,.topcoat-switch__toggle:after{content:'';position:absolute;z-index:-1;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box}.switch--disabled,.topcoat-switch__input:disabled+.topcoat-switch__toggle{opacity:.3;cursor:default;pointer-events:none}.topcoat-switch{font-size:12px;padding:0 .563rem;border-radius:4px;border:1px solid #333434;overflow:hidden;width:3.5rem}.topcoat-switch__toggle:before,.topcoat-switch__toggle:after{top:-1px;width:2.6rem}.topcoat-switch__toggle:before{content:'ON';color:#288edf;background-color:#3f4041;right:.8rem;padding-left:.75rem}.topcoat-switch__toggle{line-height:1.313rem;height:1.313rem;width:1rem;border-radius:4px;color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);background-color:#595b5b;border:1px solid #333434;margin-left:-.6rem;margin-bottom:-1px;margin-top:-1px;box-shadow:inset 0 1px #737373;-webkit-transition:margin-left .05s ease-in-out;transition:margin-left .05s ease-in-out}.topcoat-switch__toggle:after{content:'OFF';background-color:#3f4041;left:.8rem;padding-left:.6rem}.topcoat-switch__input:checked+.topcoat-switch__toggle{margin-left:1.85rem}.topcoat-switch__input:active+.topcoat-switch__toggle{border:1px solid #333434;box-shadow:inset 0 1px #737373}.topcoat-switch__input:focus+.topcoat-switch__toggle{border:1px solid #0036ff;box-shadow:0 0 0 2px #6fb5f1}.topcoat-switch__input:disabled+.topcoat-switch__toggle:after,.topcoat-switch__input:disabled+.topcoat-switch__toggle:before{background:transparent}.button,.topcoat-tab-bar__button{position:relative;display:inline-block;vertical-align:top;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;text-decoration:none}.button--quiet{background:transparent;border:1px solid transparent;box-shadow:none}.button--disabled,.topcoat-tab-bar__button:disabled{opacity:.3;cursor:default;pointer-events:none}.button-bar,.topcoat-tab-bar{display:table;table-layout:fixed;white-space:nowrap;margin:0;padding:0}.button-bar__item,.topcoat-tab-bar__item{display:table-cell;width:auto;border-radius:0}.button-bar__item>input,.topcoat-tab-bar__item>input{position:absolute;overflow:hidden;padding:0;border:0;opacity:.001;z-index:1;vertical-align:top;outline:0}.button-bar__button{border-radius:inherit}.button-bar__item:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-tab-bar__button{padding:0 .563rem;height:1.313rem;line-height:1.313rem;letter-spacing:0;color:#c6c8c8;text-shadow:0 -1px rgba(0,0,0,.69);vertical-align:top;background-color:#595b5b;box-shadow:inset 0 1px #737373;border-top:1px solid #333434}.topcoat-tab-bar__button:active,.topcoat-tab-bar__button--large:active,:checked+.topcoat-tab-bar__button{color:#288edf;background-color:#3f4041;box-shadow:inset 0 0 1px rgba(0,0,0,.05)}.topcoat-tab-bar__button:focus,.topcoat-tab-bar__button--large:focus{z-index:1;box-shadow:inset 0 1px rgba(255,255,255,.36),0 0 0 2px #6fb5f1;outline:0}.input,.topcoat-text-input,.topcoat-text-input--large{padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;vertical-align:top;outline:0}.input:disabled,.topcoat-text-input:disabled,.topcoat-text-input--large:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-text-input,.topcoat-text-input--large{line-height:1.313rem;font-size:12px;letter-spacing:0;padding:0 .563rem;border:1px solid #333434;border-radius:4px;background-color:#454646;box-shadow:inset 0 1px rgba(0,0,0,.05);color:#c6c8c8;vertical-align:top}.topcoat-text-input:focus,.topcoat-text-input--large:focus{background-color:#595b5b;color:#fff;border:1px solid #0036ff;box-shadow:0 0 0 2px #6fb5f1}.topcoat-text-input:disabled::-webkit-input-placeholder{color:#fff}.topcoat-text-input:disabled::-moz-placeholder{color:#fff}.topcoat-text-input:disabled:-ms-input-placeholder{color:#fff}.topcoat-text-input:invalid{border:1px solid #ec514e}.topcoat-text-input--large{line-height:1.688rem;font-size:.875rem}.topcoat-text-input--large:disabled{color:#fff}.topcoat-text-input--large:disabled::-webkit-input-placeholder{color:#fff}.topcoat-text-input--large:disabled::-moz-placeholder{color:#fff}.topcoat-text-input--large:disabled:-ms-input-placeholder{color:#fff}.topcoat-text-input--large:invalid{border:1px solid #ec514e}.textarea{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;vertical-align:top;resize:none;outline:0}.textarea:disabled{opacity:.3;cursor:default;pointer-events:none}.textarea,.topcoat-textarea,.topcoat-textarea--large{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;vertical-align:top;resize:none;outline:0}.textarea:disabled,.topcoat-textarea:disabled,.topcoat-textarea--large:disabled{opacity:.3;cursor:default;pointer-events:none}.topcoat-textarea,.topcoat-textarea--large{padding:1rem;font-size:1rem;font-weight:400;border-radius:4px;line-height:1.313rem;border:1px solid #333434;background-color:#454646;box-shadow:inset 0 1px rgba(0,0,0,.05);color:#c6c8c8;letter-spacing:0}.topcoat-textarea:focus,.topcoat-textarea--large:focus{background-color:#595b5b;color:#fff;border:1px solid #0036ff;box-shadow:0 0 0 2px #6fb5f1}.topcoat-textarea:disabled::-webkit-input-placeholder{color:#fff}.topcoat-textarea:disabled::-moz-placeholder{color:#fff}.topcoat-textarea:disabled:-ms-input-placeholder{color:#fff}.topcoat-textarea--large{font-size:1.3rem;line-height:1.688rem}.topcoat-textarea--large:disabled{color:#fff}.topcoat-textarea--large:disabled::-webkit-input-placeholder{color:#fff}.topcoat-textarea--large:disabled::-moz-placeholder{color:#fff}.topcoat-textarea--large:disabled:-ms-input-placeholder{color:#fff}@font-face{font-family:"Source Sans";src:url(../font/SourceSansPro-Regular.otf)}@font-face{font-family:"Source Sans";src:url(../font/SourceSansPro-Light.otf);font-weight:200}@font-face{font-family:"Source Sans";src:url(../font/SourceSansPro-Semibold.otf);font-weight:600}body{margin:0;padding:0;background:#4b4d4e;color:#000;font:16px "Source Sans",helvetica,arial,sans-serif;font-weight:400}:focus{outline-color:transparent;outline-style:none}.topcoat-icon--menu-stack{background:url(../img/hamburger_light.svg) no-repeat;background-size:cover}.quarter{width:25%}.half{width:50%}.three-quarters{width:75%}.third{width:33.333%}.two-thirds{width:66.666%}.full{width:100%}.left{text-align:left}.center{text-align:center}.right{text-align:right}.reset-ui{-moz-box-sizing:border-box;box-sizing:border-box;background-clip:padding-box;position:relative;display:inline-block;vertical-align:top;padding:0;margin:0;font:inherit;color:inherit;background:transparent;border:0;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden} \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/icons/iconDarkNormal.png b/openpype/hosts/aftereffects/api/extension/icons/iconDarkNormal.png new file mode 100644 index 0000000000000000000000000000000000000000..b8652a85b842b12d45e495c1d496590da0e26f01 GIT binary patch literal 18659 zcmeI32{e@L`^O)Von#4>8WfUQZOs@X#+of#M%u>AGh?#MG&41p5|xnNBoz^95g`?& zgtC=NNPEZzyJB4-#Ihq%yQk=eSPoe{@mAnJlP5EC^EPEdg0LUm!yhVW2!)gE^?aCk$moN8c za+w@|CJW|3B*Iv1CXEpY0zgP}x(D6U?x~xGds4|)?DxGMl6kzUcdqrB#Wq;oKbBNU?c*M zU#wLjaRm?wxhEqDR5+(qpytMm&vjIX;fpxlDU)%#C(?<@4ivYg+HC04169JeK7;6pqn*sTaD|XocBov@b zbF9GvkKq8y!JB3eoV^HCG$_av0+O--$}Ki}5g-;0th=GD9Rln)1Ss1Mc;nwKE>P&v z=aV{Ah%Z@ywTbeTgl~}W_D0OpYj99qtZYJ_MowLds0ux#j)^jw(J^=%05TI*__G}x z4QY@sXlO9mQ!Kw0{`#u;xR#$^*VyyMJl0YG=;TH;jTs<|W=EQdM+S`@Ti7S&zeXl= z<8VB!aK_RLK<2Av??KiC8=IqhOP)M=-rfCpS&fx1`Ih&VF?!Y73h&XtzasFThKD+? zJX;WjSsi63Hqudb`@K`v+{S%UJN#N-B-nkrC^P;^P)^6}ol+bProLx^42~ULQtfq*5aMV>GaVwq;1HeEjv+};4q-do7 z_RdD$c)!J{UFJd{lIE~)0|2bCUV!ktRb){s2>{laQTk^s=e(~}GpG=sTRFY8Qs&b- zlO0wIYO1UhtfZ-tbJ^<_vy`cn=nl6kU9F@X$MZUuXZ}^A=7&MYkn9b# zA1Wp&Ar-=7i8ZDR6yt~(-IBf29L}BaysLQE_HMAd_A7%^X-4+5wUcf(Qv(Vmu=@>Q zi*H1Cr*~I(8+Om>)*SGaPWM~7;lzl?Gd=fP>zAoNS9ospTpUcnSb7~#b$^<3)N`hB z!b+>tM~mIhxW(is8Es6()^DHX;Fxv#*nxYq{Z;*k8E0N{FVB2>V4h3z9q+q!uk2nX zzZ8AXla<@4vb{EzuBV=+CZR@G=c%1ZaXP7fFJ8UY6J?5ilhSi&#v!H`NxxWsvr6JE z=>y9S=p2}%kMum1dos5zSH^45tIjjw)EbX11-4#>9$lxT?jOwa%1vMX#$%2L{ghcw zOfEf#>1lBy!e=y2+^5>9+Ld_1E%V&kuCnVe8H##Qh=FE+T;9%&QN}eo=jxwq-lSie zGOAG;Z=QSVD3)lr%+R~YeQ-a`LZT~W!^mN-i&T$ZcJg4vt~TZVMb8$^Icelnf(BOw zyxcuBmY zI=^=AOLccHIl1VhnUk4QZT;=~WAzUY8K;?}A7OgZdeR=HwO?GfGGwJeaZd3i(4nw< z8u;uu~H*~bDph?TY+m!Z8+R#vE|zP zgRhWuQlmoSW5cdurMg8mjC7B*dvdk36(`VAh=!tq#9`W+w9SQOaa(s1!tZsBnwD(SnJ*-&L>?U-wF|VnW$Z5C!+xnn& zTN)khiai5gW~@)hMXbj+n_f1$Y;hey^SS+ilumL8bexMt=ogp-2VQCa)H@jSEd7~G zSGZ~l^2Tab=+TC%_tDw2P9YQxa0iz<#kwrn|JG7LKWEnNg#${wg?pq!t2CQ>2JH2n!}k4|SY{l$;F@8U!Sj+Gx~KM*ImtOaO~Yl(O5Fqt!kFXA(cAYW z?%iOOtLdCnOn*&(LEBW94@MV`T?>xCh@5vo;$}e6-u&r4aOq;JlP^72>OInzD5Qhl zw5#=Tx3CBEG8WhO-Y&Y;ve)!*|B>-t#VW#>FS4WTX4T`JQT z?_43CkH565SU>22YhJH+azRA7PMC8*)SAj$+bS=J1iX>)l71e&bMs3xmyeXQ3S@_bGV^VZcvt^3cP%Bv}%Hq!3WUcG38XZXq=%uRlg zEFUcQr1`vD`-=0uLmzBfkjeFWEv4Iwi6zIc;xCnf**6>N%`7}mc)s`i+tZ})?$Zn8 zMm-7SK1Ms86$D(12g1FpQBL_bQtv>A%w4t|F z^seZrgtmO~qmug%H13*vx+gqb{KihccK(&I^nM;(&P2}ZkHgoWX|t?DyI&1UwGYpe z!!>W(9iHDQac}-64Y~$$y;bT!s%@G_>PKHc-^;GI2~7`Qw}pMOYneaeuo2!TblITi zO-A#pZTFJs1C6`W52SC2K>XFZuH{&9cn_taefdxCY~9g@y3!{+-M-Uhu4QZAFYUE_LVOzYz`}Q|^>Kge*7X&< z%)zEsyJ7L@?TurJLkTm8(-RIQ)a{hp`6got0gHWR!5)3}DVx|xZPdw79XmEwSD-oz z88PzU&7+2}PNeK&}V5wRb z1ZjK-jxP`da>=lepuk`bF2r2xvo4PRd!iYs1^e8E8(^+wIWZuN)@@>Rzg_{g@lM=2}`41ATjanO6|&+d#pbFYNdfks)Lj5{*D1e-q^FEI2GE=$DZkuFWR? zk$joiF9+m!hO$7UJIG;fWK%$!O<*ur`&S1+rF_$2ZDa?2=0l|*!9XyGAHv}uJo=Z1 z!1{Uz@-6&_!}#X@?d-V>+V{ozA$G#^+b}SM@td#-&t%x=RG3^LJU{<%L^ep~GTEL? zW*}iQ%f8<~@o%5>oXHeM@WlPpB0Lh>|NCNpmjbQHT#zu4jBqpxjzN3!GttNthc;b| z;%6mlipXS7h=wyi@u_4k`By4@A;YOEKlFv_FsTe$=s!~V-t$KdXJ?#4Fo#PHrhpFC z1peF+3m?@1ZlLd(o@2Jl6GY?_+^M3 zIHk|T(f@CU_=U_*L)kMp{NxFpl2rWE`dnx{zzsjPesu&gJ{JrYnau$w_My4f*NynC z!h9T)3C|6mn3yYW`&u|DVz2lYfj7IPP{>cGQ)Q;~o1!yu5rX5N z9{8n-{JAXiV}5Fb_9so0`M`~z`WBRF)b8yfmO(kqz3C1Adc z25FuWHF2dh3DtvtQ4bRMQ-d}^doO2%fPo}y2LE)2Y~<)YzlD+<^%BxaY29x zHiftZ^MQDUxFA3Tn?hWI`9QovTo53FO(8D9d>~#SE(j38rVy84J`k@E7X*l4Q;172 zABb0o3j##2Da0k155z0P1py-16yg%h2jUgtf&dY03ULYM1Mv!RL4XK0g}4Osfp~?u zAV370LR^CRK)ga+5Fmn0AuhpuAYLIZ2oS-h5SL&+5U&sy1c+c$h)XaZh*yXU0z|MW z#3h&y#4E%F0V3EG;u6dU;uYe801<2oaS7%F@d|N4fCx5)xCHZoc!jtiKm?mYT!Q&P zyh27rV9r1pUvg*AHq#m(f8#)zzd_;yE_9w*g^n^+ztSLjq|@>0l-ES z0K8rY0JszYP-X60d(9RArePecEj>e;|9pbB_i|GMI{Kr1hgf^3NpM1l#MJFgFtJ2O z`)(a;D;FtCqN9g3MNZC&oD?v;I^Q%>L3agWE%&1~ha6z$pY5Sa{g^QI#y`pUT_F7pu`_=ab)E?r_{9l8-6 zW>=mmUFmkl6jL-Ad7XB=XR{0Z6l#and6>BiS4s7Z`N{XclMe_@Qr(1%QSS?xX}UpF zbmPb))wi?HoDC)MK4F3>C-x+~S-J$#IsL^Ob8IcjjsCttP3qD7y!GrpCEhzgBc_aW zxH<`?tFykmt1IL_z+H&5y7F#iNA;zI58}vsy_)?jDSdmd?pKXioZnc# zyRzek?A6#N>$bk*l}S<&8mr?fO1J4sTSOnNS)Y5f1E)ZUY^&)gFRhC&?--3+Q8UutR%CX+eE1^Bu(8XK;BU3HY*t7XB4VD7%R!B}@?(q46vJ(f mi1&-AtTd~Gk8G76HwA3Agc&1`kozZ|6?d?4u`aM$x9wjv@5eL% literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/extension/icons/iconDarkRollover.png b/openpype/hosts/aftereffects/api/extension/icons/iconDarkRollover.png new file mode 100644 index 0000000000000000000000000000000000000000..49edd7ca278ed7d0047effd37242f2bed1e9be27 GIT binary patch literal 18663 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 literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/extension/icons/iconDisabled.png b/openpype/hosts/aftereffects/api/extension/icons/iconDisabled.png new file mode 100644 index 0000000000000000000000000000000000000000..49edd7ca278ed7d0047effd37242f2bed1e9be27 GIT binary patch literal 18663 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 literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/extension/icons/iconNormal.png b/openpype/hosts/aftereffects/api/extension/icons/iconNormal.png new file mode 100644 index 0000000000000000000000000000000000000000..199326f2eac3fee033a163b7e3fe395057746c4c GIT binary patch literal 18225 zcmeI3c{o(<|HqFOyGSHip0OlkHfuADv5Yla7@>X4X0pu0)C@`_qJ^ZgMOq|lMUhaJ zqEe)uREX@!gC0vf6@G&jooas1@A`dzfBdd%=DKG2+@JgNzR&%-KlkUHGuL&ZHrQK= z39l3e06@&z%FK!L4V!rj@^e02i`N)%zJwT7?*0HEvTWwf2PBsp8T%VKsNF+q+=Kp~9*RKtiy9q~t%T!Us$QV=!x! z%LQ%&eC%f;f|KLBNjtDk^N?U?L!8G3<)@drh81uO^37 zVm_u=6=fc}SrEEQz||F|s9t9+vqnmvuz-+c466)Cl+_N^6@N4G2msPzr8%=5nPk^3 z$*Zf=k1kxY9s2GL|Fp7)N9R;`eKx}w0NRVgYHJj2@S zS<^#Cla^_#fDnrHiCqA&%}f=Be^_8tBM1OyX`z}|P2@%^R%n&;%U3LJsSx?RL;s+u z>fK6H2~%NGh`iqpjhq9f>Jg8xDroM2|7onCTkhx?5yw~pt8}@!#E>EWksz2_pD}`Z0zXhm6 zTP$wHuDcItANU+mYKc4|a`4hRCbxE^QJr5L$T9v2wL?++~~VYPXjDb9aS@Hu8|0m6l4Q zWXv+SL{KE|u7Rp#6i$0}@$m)L*Dm6pNg%M^r zrn%<|ovt_@y1YzxcM|Gx*m7%|^xX5Oo~`th@f@dJdCR=D^z|u4`}ilWPiy-u-^RaP zILZPYbeuagh5kI9?UI$Uq2F1~nVPxo@}Vs1 zWjfyIVzB#U6u*0wZIuJ=qGQ^%?VY975D}tm0b5JSODsELcc|W7wQG-G?cJkUk}#=I za>Otz^BfANvtGxwz-iO@>I-W8YD#K0cJJ&4 zlbq~|FKJv_XS>d}=JBJ)=N~sF>LnW@UugFx_a?tcZY$cciM>gy@N(fzvUPsfrc3UF zg{8jXn^5lQZZ4a)7Fgt_YXP=$gNUgg1cj1G=%p%#V-7oo@^9p?m z_YtTbGSs4bc~ygPWpVp%?K?JUC?Iy|)sk$5O$x#O*Zri>8_}&vb!P^R_T3vj-3O<- z)l1aB)afi-R;zKBmg1cJ%#tcYE%px^i98ZzbD$O$kA)@6RlmMp*0r8Fwrgv9;M1{` zQT2K5JNgNItqKGHp_k?OAF|D@_Z?u}!2gOSMDQwXoNoL~o>Ln!q|uL_(9 zk9Ey`akVI&l{HQnlEHq!`kn2>c47=#wOie{KQbsUx?fc5P(@GO9^QuAuQE-V4xDZP zGD24IQ5QyqnuKm%@M$7&uhN}pBXl$QL;kA)lcQE!qJ;x06)LI9@CN;c$d|Y+ znu_+rR+@H!C;o~p)eBI)r<1PLU3_qL=J8TnG27S4=+xy&dmujAhUhY6*ooNVyG*l` z?BWWk@2EYLJ+(RH@cgNJzDJ7Sil+n~coiJaS?mrKE;POL7QadTg{DA0mF!Bn^Em1u z>U4JMn#Ti=3LZ8eH#jqNc6vavQnLL-VS0p(D^$U)rz~eDDj%~epjo(6dhwcwjr=*7 zn}-WEeV#jH54gtX1(&G>+IfX;t9W>z;s&2rzle))clf^kmKOtC9o*pVZ8r!XBU0qB z(ou)=q?%>orT4#7Z((=K9il(Hlh|_ddS>?BVp2WjDW$Kc6`Hzv$?2^4p71^a{71b0;M3PP2rnjl8wTZq zNG&k}!R=-jJ{P;^l;`A6)}IQ-jq;+h`X)|)^4Xee@3U*5X5rI?lL9JB z3SSie`CQ?tA>JvbaZSJFlA1qmPo)g8pkn%BF2~MPcc?JT0=oLfh1$jy#n4TAjt1ql z3q1Q{j{;QzzSA^mILRW}IqB18kIlCn9$_0A-?avQwru`G{ERNfJz)I^{sFD2@4&M- z>Tvzhlv64Df?@x(>}Wn;7}QIwYuhPKAG#&MY=3G#eWZ6?>AJct!#A13`OV$STyMH2 zT|sa6dB=Q*nTmVnP*ZZIBq^_Wi&ybrVA+iz%6Ok#szKfB(XxQQmrpl7PemENE*lA? zkKY>~Yg_(0*l+b@U2VxL*6P9BQisy*qs9X!uW+vqJvZ7s)$($vW&h4{R@z8Ii{&_f zcv$^Z>{yIAZgEUvOl^c%M1Sg9EDF_ODUJ*@nuqx@gYtnP#{$WVQ>_%TU>5W8iPrr`$A@P2_E#_ zOhaYmnSs8&zRb&q@ogYq|1az~6yaB*tsnG ze*eU|eNwk05NW^O-}A}|U6M&*aTU>!P%MhW;=D&Kqls9|S^w)XXB5`2kdYcnio?l2mS#7T4= ziU*mb1Jxtz5uv&U6cUs`BzQnkNF54dt-gUS386DP$sfaik~X6gch6+e7ikhl8i~@? zChF-Dpk#eAM_QZn;X&{~K=lle9>ldCYsq8^<*W3(@Smg|{AipqMDU*1XXfbtr$hWg z=BJ^oX#Sk!37D5uoYa~vG|uE*Kem2#c++MJ27}<|PoCL_hRR-Qr5bPAJ2 z@FSa0IXUs)Zp1msA7jAU82C(G_NM(Gtb#vb&;M=>{@o_}|Gx&`^OxvJ@THPTSoqh} z{o3&FmThh^f80}FOTo{35JMuODRe&{0uxK~AyCP1hA$NZ|K9k0Ow6})5gJGLru*5^ zNn|Xie*LQXmr3MW z;coJ9apwc_@^FEGa5s6lxbp#ddALA8xSKp&-1&gKJX|0k+)W-X?tDOA9xf0N?j{cx zcRnC54;Kgscaw*UJ0FmjhYJLRyUD}Foe#*%!vzAu-Q?lo&Ijb>;Q|5SZt`$(=L7Qc zaDjkuH+i_Y^8tBzxIjR-n><|H`GCAUTp%FaO&%`pd_Z0vE)WpzCJz^PJ|Hg-7YGP< zlPfOa@6UUZeK`+#vN%t3ept0Jg!3dQglOet2LOSq03aj`0REZgeD?vsZUg|l+rfE= zGywo)=qI+{vjBhv2G(XKcy`lYuZ*l*9OVVx$23r)!lX(o1RepJLKy+bnV+!qua%UI|OcbuEDn3 zG+Lj=mcITYRSC7F%0)D7_u0&ZlX&k6wXCDC$5v~n-SnBa@H~xW>zZJ6RytTGr~cR@W5&gZv*P_; zqOxMC`1eXoiv>fXsb&*Gq81yS#rR)@z7HNX;~#ppb=zNyhmKr7F<^6mWhp#lf1p<- zyf^Mmu~mDqDtq(A_l2qx8!x`kRhna361pv^v3HfwZ7Get8bVWwwXx^o z!}^UU;Eyc%&x+^W&Ho_XsBCg&u|rniXBCQaX#FCGcMe6pdzwhc9+@&oAA7XkXl#@_ zxJ_(C<{{1DQTtN;{-dp;BXy^qrK(dT#jiT99<@lI52uAaEqtT)UXC?z{lU&tboPP1 ziwg^wUKv&UTnBu8kZ|(bMv9LPyhEooGso!mC%dg9Q|xQ7;N^;Ebk|7+r7NwIOdtCj rzoP8jRRMg`>7F{jWm9LXrp17~2aKe|@S1C!bOWr-?alH`cO3XHL3dXp literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/extension/icons/iconRollover.png b/openpype/hosts/aftereffects/api/extension/icons/iconRollover.png new file mode 100644 index 0000000000000000000000000000000000000000..ff62645798592fa733626e43741031a57d34c61a GIT binary patch literal 18664 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 literal 0 HcmV?d00001 diff --git a/openpype/hosts/aftereffects/api/extension/index.html b/openpype/hosts/aftereffects/api/extension/index.html new file mode 100644 index 0000000000..9e39bf1acc --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/index.html @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/js/libs/CSInterface.js b/openpype/hosts/aftereffects/api/extension/js/libs/CSInterface.js new file mode 100644 index 0000000000..4239391efd --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/js/libs/CSInterface.js @@ -0,0 +1,1193 @@ +/************************************************************************************************** +* +* ADOBE SYSTEMS INCORPORATED +* Copyright 2013 Adobe Systems Incorporated +* All Rights Reserved. +* +* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the +* terms of the Adobe license agreement accompanying it. If you have received this file from a +* source other than Adobe, then your use, modification, or distribution of it requires the prior +* written permission of Adobe. +* +**************************************************************************************************/ + +/** CSInterface - v8.0.0 */ + +/** + * Stores constants for the window types supported by the CSXS infrastructure. + */ +function CSXSWindowType() +{ +} + +/** Constant for the CSXS window type Panel. */ +CSXSWindowType._PANEL = "Panel"; + +/** Constant for the CSXS window type Modeless. */ +CSXSWindowType._MODELESS = "Modeless"; + +/** Constant for the CSXS window type ModalDialog. */ +CSXSWindowType._MODAL_DIALOG = "ModalDialog"; + +/** EvalScript error message */ +EvalScript_ErrMessage = "EvalScript error."; + +/** + * @class Version + * Defines a version number with major, minor, micro, and special + * components. The major, minor and micro values are numeric; the special + * value can be any string. + * + * @param major The major version component, a positive integer up to nine digits long. + * @param minor The minor version component, a positive integer up to nine digits long. + * @param micro The micro version component, a positive integer up to nine digits long. + * @param special The special version component, an arbitrary string. + * + * @return A new \c Version object. + */ +function Version(major, minor, micro, special) +{ + this.major = major; + this.minor = minor; + this.micro = micro; + this.special = special; +} + +/** + * The maximum value allowed for a numeric version component. + * This reflects the maximum value allowed in PlugPlug and the manifest schema. + */ +Version.MAX_NUM = 999999999; + +/** + * @class VersionBound + * Defines a boundary for a version range, which associates a \c Version object + * with a flag for whether it is an inclusive or exclusive boundary. + * + * @param version The \c #Version object. + * @param inclusive True if this boundary is inclusive, false if it is exclusive. + * + * @return A new \c VersionBound object. + */ +function VersionBound(version, inclusive) +{ + this.version = version; + this.inclusive = inclusive; +} + +/** + * @class VersionRange + * Defines a range of versions using a lower boundary and optional upper boundary. + * + * @param lowerBound The \c #VersionBound object. + * @param upperBound The \c #VersionBound object, or null for a range with no upper boundary. + * + * @return A new \c VersionRange object. + */ +function VersionRange(lowerBound, upperBound) +{ + this.lowerBound = lowerBound; + this.upperBound = upperBound; +} + +/** + * @class Runtime + * Represents a runtime related to the CEP infrastructure. + * Extensions can declare dependencies on particular + * CEP runtime versions in the extension manifest. + * + * @param name The runtime name. + * @param version A \c #VersionRange object that defines a range of valid versions. + * + * @return A new \c Runtime object. + */ +function Runtime(name, versionRange) +{ + this.name = name; + this.versionRange = versionRange; +} + +/** +* @class Extension +* Encapsulates a CEP-based extension to an Adobe application. +* +* @param id The unique identifier of this extension. +* @param name The localizable display name of this extension. +* @param mainPath The path of the "index.html" file. +* @param basePath The base path of this extension. +* @param windowType The window type of the main window of this extension. + Valid values are defined by \c #CSXSWindowType. +* @param width The default width in pixels of the main window of this extension. +* @param height The default height in pixels of the main window of this extension. +* @param minWidth The minimum width in pixels of the main window of this extension. +* @param minHeight The minimum height in pixels of the main window of this extension. +* @param maxWidth The maximum width in pixels of the main window of this extension. +* @param maxHeight The maximum height in pixels of the main window of this extension. +* @param defaultExtensionDataXml The extension data contained in the default \c ExtensionDispatchInfo section of the extension manifest. +* @param specialExtensionDataXml The extension data contained in the application-specific \c ExtensionDispatchInfo section of the extension manifest. +* @param requiredRuntimeList An array of \c Runtime objects for runtimes required by this extension. +* @param isAutoVisible True if this extension is visible on loading. +* @param isPluginExtension True if this extension has been deployed in the Plugins folder of the host application. +* +* @return A new \c Extension object. +*/ +function Extension(id, name, mainPath, basePath, windowType, width, height, minWidth, minHeight, maxWidth, maxHeight, + defaultExtensionDataXml, specialExtensionDataXml, requiredRuntimeList, isAutoVisible, isPluginExtension) +{ + this.id = id; + this.name = name; + this.mainPath = mainPath; + this.basePath = basePath; + this.windowType = windowType; + this.width = width; + this.height = height; + this.minWidth = minWidth; + this.minHeight = minHeight; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.defaultExtensionDataXml = defaultExtensionDataXml; + this.specialExtensionDataXml = specialExtensionDataXml; + this.requiredRuntimeList = requiredRuntimeList; + this.isAutoVisible = isAutoVisible; + this.isPluginExtension = isPluginExtension; +} + +/** + * @class CSEvent + * A standard JavaScript event, the base class for CEP events. + * + * @param type The name of the event type. + * @param scope The scope of event, can be "GLOBAL" or "APPLICATION". + * @param appId The unique identifier of the application that generated the event. + * @param extensionId The unique identifier of the extension that generated the event. + * + * @return A new \c CSEvent object + */ +function CSEvent(type, scope, appId, extensionId) +{ + this.type = type; + this.scope = scope; + this.appId = appId; + this.extensionId = extensionId; +} + +/** Event-specific data. */ +CSEvent.prototype.data = ""; + +/** + * @class SystemPath + * Stores operating-system-specific location constants for use in the + * \c #CSInterface.getSystemPath() method. + * @return A new \c SystemPath object. + */ +function SystemPath() +{ +} + +/** The path to user data. */ +SystemPath.USER_DATA = "userData"; + +/** The path to common files for Adobe applications. */ +SystemPath.COMMON_FILES = "commonFiles"; + +/** The path to the user's default document folder. */ +SystemPath.MY_DOCUMENTS = "myDocuments"; + +/** @deprecated. Use \c #SystemPath.Extension. */ +SystemPath.APPLICATION = "application"; + +/** The path to current extension. */ +SystemPath.EXTENSION = "extension"; + +/** The path to hosting application's executable. */ +SystemPath.HOST_APPLICATION = "hostApplication"; + +/** + * @class ColorType + * Stores color-type constants. + */ +function ColorType() +{ +} + +/** RGB color type. */ +ColorType.RGB = "rgb"; + +/** Gradient color type. */ +ColorType.GRADIENT = "gradient"; + +/** Null color type. */ +ColorType.NONE = "none"; + +/** + * @class RGBColor + * Stores an RGB color with red, green, blue, and alpha values. + * All values are in the range [0.0 to 255.0]. Invalid numeric values are + * converted to numbers within this range. + * + * @param red The red value, in the range [0.0 to 255.0]. + * @param green The green value, in the range [0.0 to 255.0]. + * @param blue The blue value, in the range [0.0 to 255.0]. + * @param alpha The alpha (transparency) value, in the range [0.0 to 255.0]. + * The default, 255.0, means that the color is fully opaque. + * + * @return A new RGBColor object. + */ +function RGBColor(red, green, blue, alpha) +{ + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; +} + +/** + * @class Direction + * A point value in which the y component is 0 and the x component + * is positive or negative for a right or left direction, + * or the x component is 0 and the y component is positive or negative for + * an up or down direction. + * + * @param x The horizontal component of the point. + * @param y The vertical component of the point. + * + * @return A new \c Direction object. + */ +function Direction(x, y) +{ + this.x = x; + this.y = y; +} + +/** + * @class GradientStop + * Stores gradient stop information. + * + * @param offset The offset of the gradient stop, in the range [0.0 to 1.0]. + * @param rgbColor The color of the gradient at this point, an \c #RGBColor object. + * + * @return GradientStop object. + */ +function GradientStop(offset, rgbColor) +{ + this.offset = offset; + this.rgbColor = rgbColor; +} + +/** + * @class GradientColor + * Stores gradient color information. + * + * @param type The gradient type, must be "linear". + * @param direction A \c #Direction object for the direction of the gradient + (up, down, right, or left). + * @param numStops The number of stops in the gradient. + * @param gradientStopList An array of \c #GradientStop objects. + * + * @return A new \c GradientColor object. + */ +function GradientColor(type, direction, numStops, arrGradientStop) +{ + this.type = type; + this.direction = direction; + this.numStops = numStops; + this.arrGradientStop = arrGradientStop; +} + +/** + * @class UIColor + * Stores color information, including the type, anti-alias level, and specific color + * values in a color object of an appropriate type. + * + * @param type The color type, 1 for "rgb" and 2 for "gradient". + The supplied color object must correspond to this type. + * @param antialiasLevel The anti-alias level constant. + * @param color A \c #RGBColor or \c #GradientColor object containing specific color information. + * + * @return A new \c UIColor object. + */ +function UIColor(type, antialiasLevel, color) +{ + this.type = type; + this.antialiasLevel = antialiasLevel; + this.color = color; +} + +/** + * @class AppSkinInfo + * Stores window-skin properties, such as color and font. All color parameter values are \c #UIColor objects except that systemHighlightColor is \c #RGBColor object. + * + * @param baseFontFamily The base font family of the application. + * @param baseFontSize The base font size of the application. + * @param appBarBackgroundColor The application bar background color. + * @param panelBackgroundColor The background color of the extension panel. + * @param appBarBackgroundColorSRGB The application bar background color, as sRGB. + * @param panelBackgroundColorSRGB The background color of the extension panel, as sRGB. + * @param systemHighlightColor The highlight color of the extension panel, if provided by the host application. Otherwise, the operating-system highlight color. + * + * @return AppSkinInfo object. + */ +function AppSkinInfo(baseFontFamily, baseFontSize, appBarBackgroundColor, panelBackgroundColor, appBarBackgroundColorSRGB, panelBackgroundColorSRGB, systemHighlightColor) +{ + this.baseFontFamily = baseFontFamily; + this.baseFontSize = baseFontSize; + this.appBarBackgroundColor = appBarBackgroundColor; + this.panelBackgroundColor = panelBackgroundColor; + this.appBarBackgroundColorSRGB = appBarBackgroundColorSRGB; + this.panelBackgroundColorSRGB = panelBackgroundColorSRGB; + this.systemHighlightColor = systemHighlightColor; +} + +/** + * @class HostEnvironment + * Stores information about the environment in which the extension is loaded. + * + * @param appName The application's name. + * @param appVersion The application's version. + * @param appLocale The application's current license locale. + * @param appUILocale The application's current UI locale. + * @param appId The application's unique identifier. + * @param isAppOnline True if the application is currently online. + * @param appSkinInfo An \c #AppSkinInfo object containing the application's default color and font styles. + * + * @return A new \c HostEnvironment object. + */ +function HostEnvironment(appName, appVersion, appLocale, appUILocale, appId, isAppOnline, appSkinInfo) +{ + this.appName = appName; + this.appVersion = appVersion; + this.appLocale = appLocale; + this.appUILocale = appUILocale; + this.appId = appId; + this.isAppOnline = isAppOnline; + this.appSkinInfo = appSkinInfo; +} + +/** + * @class HostCapabilities + * Stores information about the host capabilities. + * + * @param EXTENDED_PANEL_MENU True if the application supports panel menu. + * @param EXTENDED_PANEL_ICONS True if the application supports panel icon. + * @param DELEGATE_APE_ENGINE True if the application supports delegated APE engine. + * @param SUPPORT_HTML_EXTENSIONS True if the application supports HTML extensions. + * @param DISABLE_FLASH_EXTENSIONS True if the application disables FLASH extensions. + * + * @return A new \c HostCapabilities object. + */ +function HostCapabilities(EXTENDED_PANEL_MENU, EXTENDED_PANEL_ICONS, DELEGATE_APE_ENGINE, SUPPORT_HTML_EXTENSIONS, DISABLE_FLASH_EXTENSIONS) +{ + this.EXTENDED_PANEL_MENU = EXTENDED_PANEL_MENU; + this.EXTENDED_PANEL_ICONS = EXTENDED_PANEL_ICONS; + this.DELEGATE_APE_ENGINE = DELEGATE_APE_ENGINE; + this.SUPPORT_HTML_EXTENSIONS = SUPPORT_HTML_EXTENSIONS; + this.DISABLE_FLASH_EXTENSIONS = DISABLE_FLASH_EXTENSIONS; // Since 5.0.0 +} + +/** + * @class ApiVersion + * Stores current api version. + * + * Since 4.2.0 + * + * @param major The major version + * @param minor The minor version. + * @param micro The micro version. + * + * @return ApiVersion object. + */ +function ApiVersion(major, minor, micro) +{ + this.major = major; + this.minor = minor; + this.micro = micro; +} + +/** + * @class MenuItemStatus + * Stores flyout menu item status + * + * Since 5.2.0 + * + * @param menuItemLabel The menu item label. + * @param enabled True if user wants to enable the menu item. + * @param checked True if user wants to check the menu item. + * + * @return MenuItemStatus object. + */ +function MenuItemStatus(menuItemLabel, enabled, checked) +{ + this.menuItemLabel = menuItemLabel; + this.enabled = enabled; + this.checked = checked; +} + +/** + * @class ContextMenuItemStatus + * Stores the status of the context menu item. + * + * Since 5.2.0 + * + * @param menuItemID The menu item id. + * @param enabled True if user wants to enable the menu item. + * @param checked True if user wants to check the menu item. + * + * @return MenuItemStatus object. + */ +function ContextMenuItemStatus(menuItemID, enabled, checked) +{ + this.menuItemID = menuItemID; + this.enabled = enabled; + this.checked = checked; +} +//------------------------------ CSInterface ---------------------------------- + +/** + * @class CSInterface + * This is the entry point to the CEP extensibility infrastructure. + * Instantiate this object and use it to: + *
    + *
  • Access information about the host application in which an extension is running
  • + *
  • Launch an extension
  • + *
  • Register interest in event notifications, and dispatch events
  • + *
+ * + * @return A new \c CSInterface object + */ +function CSInterface() +{ +} + +/** + * User can add this event listener to handle native application theme color changes. + * Callback function gives extensions ability to fine-tune their theme color after the + * global theme color has been changed. + * The callback function should be like below: + * + * @example + * // event is a CSEvent object, but user can ignore it. + * function OnAppThemeColorChanged(event) + * { + * // Should get a latest HostEnvironment object from application. + * var skinInfo = JSON.parse(window.__adobe_cep__.getHostEnvironment()).appSkinInfo; + * // Gets the style information such as color info from the skinInfo, + * // and redraw all UI controls of your extension according to the style info. + * } + */ +CSInterface.THEME_COLOR_CHANGED_EVENT = "com.adobe.csxs.events.ThemeColorChanged"; + +/** The host environment data object. */ +CSInterface.prototype.hostEnvironment = window.__adobe_cep__ ? JSON.parse(window.__adobe_cep__.getHostEnvironment()) : null; + +/** Retrieves information about the host environment in which the + * extension is currently running. + * + * @return A \c #HostEnvironment object. + */ +CSInterface.prototype.getHostEnvironment = function() +{ + this.hostEnvironment = JSON.parse(window.__adobe_cep__.getHostEnvironment()); + return this.hostEnvironment; +}; + +/** Closes this extension. */ +CSInterface.prototype.closeExtension = function() +{ + window.__adobe_cep__.closeExtension(); +}; + +/** + * Retrieves a path for which a constant is defined in the system. + * + * @param pathType The path-type constant defined in \c #SystemPath , + * + * @return The platform-specific system path string. + */ +CSInterface.prototype.getSystemPath = function(pathType) +{ + var path = decodeURI(window.__adobe_cep__.getSystemPath(pathType)); + var OSVersion = this.getOSInformation(); + if (OSVersion.indexOf("Windows") >= 0) + { + path = path.replace("file:///", ""); + } + else if (OSVersion.indexOf("Mac") >= 0) + { + path = path.replace("file://", ""); + } + return path; +}; + +/** + * Evaluates a JavaScript script, which can use the JavaScript DOM + * of the host application. + * + * @param script The JavaScript script. + * @param callback Optional. A callback function that receives the result of execution. + * If execution fails, the callback function receives the error message \c EvalScript_ErrMessage. + */ +CSInterface.prototype.evalScript = function(script, callback) +{ + if(callback === null || callback === undefined) + { + callback = function(result){}; + } + window.__adobe_cep__.evalScript(script, callback); +}; + +/** + * Retrieves the unique identifier of the application. + * in which the extension is currently running. + * + * @return The unique ID string. + */ +CSInterface.prototype.getApplicationID = function() +{ + var appId = this.hostEnvironment.appId; + return appId; +}; + +/** + * Retrieves host capability information for the application + * in which the extension is currently running. + * + * @return A \c #HostCapabilities object. + */ +CSInterface.prototype.getHostCapabilities = function() +{ + var hostCapabilities = JSON.parse(window.__adobe_cep__.getHostCapabilities() ); + return hostCapabilities; +}; + +/** + * Triggers a CEP event programmatically. Yoy can use it to dispatch + * an event of a predefined type, or of a type you have defined. + * + * @param event A \c CSEvent object. + */ +CSInterface.prototype.dispatchEvent = function(event) +{ + if (typeof event.data == "object") + { + event.data = JSON.stringify(event.data); + } + + window.__adobe_cep__.dispatchEvent(event); +}; + +/** + * Registers an interest in a CEP event of a particular type, and + * assigns an event handler. + * The event infrastructure notifies your extension when events of this type occur, + * passing the event object to the registered handler function. + * + * @param type The name of the event type of interest. + * @param listener The JavaScript handler function or method. + * @param obj Optional, the object containing the handler method, if any. + * Default is null. + */ +CSInterface.prototype.addEventListener = function(type, listener, obj) +{ + window.__adobe_cep__.addEventListener(type, listener, obj); +}; + +/** + * Removes a registered event listener. + * + * @param type The name of the event type of interest. + * @param listener The JavaScript handler function or method that was registered. + * @param obj Optional, the object containing the handler method, if any. + * Default is null. + */ +CSInterface.prototype.removeEventListener = function(type, listener, obj) +{ + window.__adobe_cep__.removeEventListener(type, listener, obj); +}; + +/** + * Loads and launches another extension, or activates the extension if it is already loaded. + * + * @param extensionId The extension's unique identifier. + * @param startupParams Not currently used, pass "". + * + * @example + * To launch the extension "help" with ID "HLP" from this extension, call: + * requestOpenExtension("HLP", ""); + * + */ +CSInterface.prototype.requestOpenExtension = function(extensionId, params) +{ + window.__adobe_cep__.requestOpenExtension(extensionId, params); +}; + +/** + * Retrieves the list of extensions currently loaded in the current host application. + * The extension list is initialized once, and remains the same during the lifetime + * of the CEP session. + * + * @param extensionIds Optional, an array of unique identifiers for extensions of interest. + * If omitted, retrieves data for all extensions. + * + * @return Zero or more \c #Extension objects. + */ +CSInterface.prototype.getExtensions = function(extensionIds) +{ + var extensionIdsStr = JSON.stringify(extensionIds); + var extensionsStr = window.__adobe_cep__.getExtensions(extensionIdsStr); + + var extensions = JSON.parse(extensionsStr); + return extensions; +}; + +/** + * Retrieves network-related preferences. + * + * @return A JavaScript object containing network preferences. + */ +CSInterface.prototype.getNetworkPreferences = function() +{ + var result = window.__adobe_cep__.getNetworkPreferences(); + var networkPre = JSON.parse(result); + + return networkPre; +}; + +/** + * Initializes the resource bundle for this extension with property values + * for the current application and locale. + * To support multiple locales, you must define a property file for each locale, + * containing keyed display-string values for that locale. + * See localization documentation for Extension Builder and related products. + * + * Keys can be in the + * form key.value="localized string", for use in HTML text elements. + * For example, in this input element, the localized \c key.value string is displayed + * instead of the empty \c value string: + * + * + * + * @return An object containing the resource bundle information. + */ +CSInterface.prototype.initResourceBundle = function() +{ + var resourceBundle = JSON.parse(window.__adobe_cep__.initResourceBundle()); + var resElms = document.querySelectorAll('[data-locale]'); + for (var n = 0; n < resElms.length; n++) + { + var resEl = resElms[n]; + // Get the resource key from the element. + var resKey = resEl.getAttribute('data-locale'); + if (resKey) + { + // Get all the resources that start with the key. + for (var key in resourceBundle) + { + if (key.indexOf(resKey) === 0) + { + var resValue = resourceBundle[key]; + if (key.length == resKey.length) + { + resEl.innerHTML = resValue; + } + else if ('.' == key.charAt(resKey.length)) + { + var attrKey = key.substring(resKey.length + 1); + resEl[attrKey] = resValue; + } + } + } + } + } + return resourceBundle; +}; + +/** + * Writes installation information to a file. + * + * @return The file path. + */ +CSInterface.prototype.dumpInstallationInfo = function() +{ + return window.__adobe_cep__.dumpInstallationInfo(); +}; + +/** + * Retrieves version information for the current Operating System, + * See http://www.useragentstring.com/pages/Chrome/ for Chrome \c navigator.userAgent values. + * + * @return A string containing the OS version, or "unknown Operation System". + * If user customizes the User Agent by setting CEF command parameter "--user-agent", only + * "Mac OS X" or "Windows" will be returned. + */ +CSInterface.prototype.getOSInformation = function() +{ + var userAgent = navigator.userAgent; + + if ((navigator.platform == "Win32") || (navigator.platform == "Windows")) + { + var winVersion = "Windows"; + var winBit = ""; + if (userAgent.indexOf("Windows") > -1) + { + if (userAgent.indexOf("Windows NT 5.0") > -1) + { + winVersion = "Windows 2000"; + } + else if (userAgent.indexOf("Windows NT 5.1") > -1) + { + winVersion = "Windows XP"; + } + else if (userAgent.indexOf("Windows NT 5.2") > -1) + { + winVersion = "Windows Server 2003"; + } + else if (userAgent.indexOf("Windows NT 6.0") > -1) + { + winVersion = "Windows Vista"; + } + else if (userAgent.indexOf("Windows NT 6.1") > -1) + { + winVersion = "Windows 7"; + } + else if (userAgent.indexOf("Windows NT 6.2") > -1) + { + winVersion = "Windows 8"; + } + else if (userAgent.indexOf("Windows NT 6.3") > -1) + { + winVersion = "Windows 8.1"; + } + else if (userAgent.indexOf("Windows NT 10") > -1) + { + winVersion = "Windows 10"; + } + + if (userAgent.indexOf("WOW64") > -1 || userAgent.indexOf("Win64") > -1) + { + winBit = " 64-bit"; + } + else + { + winBit = " 32-bit"; + } + } + + return winVersion + winBit; + } + else if ((navigator.platform == "MacIntel") || (navigator.platform == "Macintosh")) + { + var result = "Mac OS X"; + + if (userAgent.indexOf("Mac OS X") > -1) + { + result = userAgent.substring(userAgent.indexOf("Mac OS X"), userAgent.indexOf(")")); + result = result.replace(/_/g, "."); + } + + return result; + } + + return "Unknown Operation System"; +}; + +/** + * Opens a page in the default system browser. + * + * Since 4.2.0 + * + * @param url The URL of the page/file to open, or the email address. + * Must use HTTP/HTTPS/file/mailto protocol. For example: + * "http://www.adobe.com" + * "https://github.com" + * "file:///C:/log.txt" + * "mailto:test@adobe.com" + * + * @return One of these error codes:\n + *
    \n + *
  • NO_ERROR - 0
  • \n + *
  • ERR_UNKNOWN - 1
  • \n + *
  • ERR_INVALID_PARAMS - 2
  • \n + *
  • ERR_INVALID_URL - 201
  • \n + *
\n + */ +CSInterface.prototype.openURLInDefaultBrowser = function(url) +{ + return cep.util.openURLInDefaultBrowser(url); +}; + +/** + * Retrieves extension ID. + * + * Since 4.2.0 + * + * @return extension ID. + */ +CSInterface.prototype.getExtensionID = function() +{ + return window.__adobe_cep__.getExtensionId(); +}; + +/** + * Retrieves the scale factor of screen. + * On Windows platform, the value of scale factor might be different from operating system's scale factor, + * since host application may use its self-defined scale factor. + * + * Since 4.2.0 + * + * @return One of the following float number. + *
    \n + *
  • -1.0 when error occurs
  • \n + *
  • 1.0 means normal screen
  • \n + *
  • >1.0 means HiDPI screen
  • \n + *
\n + */ +CSInterface.prototype.getScaleFactor = function() +{ + return window.__adobe_cep__.getScaleFactor(); +}; + +/** + * Set a handler to detect any changes of scale factor. This only works on Mac. + * + * Since 4.2.0 + * + * @param handler The function to be called when scale factor is changed. + * + */ +CSInterface.prototype.setScaleFactorChangedHandler = function(handler) +{ + window.__adobe_cep__.setScaleFactorChangedHandler(handler); +}; + +/** + * Retrieves current API version. + * + * Since 4.2.0 + * + * @return ApiVersion object. + * + */ +CSInterface.prototype.getCurrentApiVersion = function() +{ + var apiVersion = JSON.parse(window.__adobe_cep__.getCurrentApiVersion()); + return apiVersion; +}; + +/** + * Set panel flyout menu by an XML. + * + * Since 5.2.0 + * + * Register a callback function for "com.adobe.csxs.events.flyoutMenuClicked" to get notified when a + * menu item is clicked. + * The "data" attribute of event is an object which contains "menuId" and "menuName" attributes. + * + * Register callback functions for "com.adobe.csxs.events.flyoutMenuOpened" and "com.adobe.csxs.events.flyoutMenuClosed" + * respectively to get notified when flyout menu is opened or closed. + * + * @param menu A XML string which describes menu structure. + * An example menu XML: + * + * + * + * + * + * + * + * + * + * + * + * + */ +CSInterface.prototype.setPanelFlyoutMenu = function(menu) +{ + if ("string" != typeof menu) + { + return; + } + + window.__adobe_cep__.invokeSync("setPanelFlyoutMenu", menu); +}; + +/** + * Updates a menu item in the extension window's flyout menu, by setting the enabled + * and selection status. + * + * Since 5.2.0 + * + * @param menuItemLabel The menu item label. + * @param enabled True to enable the item, false to disable it (gray it out). + * @param checked True to select the item, false to deselect it. + * + * @return false when the host application does not support this functionality (HostCapabilities.EXTENDED_PANEL_MENU is false). + * Fails silently if menu label is invalid. + * + * @see HostCapabilities.EXTENDED_PANEL_MENU + */ +CSInterface.prototype.updatePanelMenuItem = function(menuItemLabel, enabled, checked) +{ + var ret = false; + if (this.getHostCapabilities().EXTENDED_PANEL_MENU) + { + var itemStatus = new MenuItemStatus(menuItemLabel, enabled, checked); + ret = window.__adobe_cep__.invokeSync("updatePanelMenuItem", JSON.stringify(itemStatus)); + } + return ret; +}; + + +/** + * Set context menu by XML string. + * + * Since 5.2.0 + * + * There are a number of conventions used to communicate what type of menu item to create and how it should be handled. + * - an item without menu ID or menu name is disabled and is not shown. + * - if the item name is "---" (three hyphens) then it is treated as a separator. The menu ID in this case will always be NULL. + * - Checkable attribute takes precedence over Checked attribute. + * - a PNG icon. For optimal display results please supply a 16 x 16px icon as larger dimensions will increase the size of the menu item. + The Chrome extension contextMenus API was taken as a reference. + https://developer.chrome.com/extensions/contextMenus + * - the items with icons and checkable items cannot coexist on the same menu level. The former take precedences over the latter. + * + * @param menu A XML string which describes menu structure. + * @param callback The callback function which is called when a menu item is clicked. The only parameter is the returned ID of clicked menu item. + * + * @description An example menu XML: + * + * + * + * + * + * + * + * + * + * + * + */ +CSInterface.prototype.setContextMenu = function(menu, callback) +{ + if ("string" != typeof menu) + { + return; + } + + window.__adobe_cep__.invokeAsync("setContextMenu", menu, callback); +}; + +/** + * Set context menu by JSON string. + * + * Since 6.0.0 + * + * There are a number of conventions used to communicate what type of menu item to create and how it should be handled. + * - an item without menu ID or menu name is disabled and is not shown. + * - if the item label is "---" (three hyphens) then it is treated as a separator. The menu ID in this case will always be NULL. + * - Checkable attribute takes precedence over Checked attribute. + * - a PNG icon. For optimal display results please supply a 16 x 16px icon as larger dimensions will increase the size of the menu item. + The Chrome extension contextMenus API was taken as a reference. + * - the items with icons and checkable items cannot coexist on the same menu level. The former take precedences over the latter. + https://developer.chrome.com/extensions/contextMenus + * + * @param menu A JSON string which describes menu structure. + * @param callback The callback function which is called when a menu item is clicked. The only parameter is the returned ID of clicked menu item. + * + * @description An example menu JSON: + * + * { + * "menu": [ + * { + * "id": "menuItemId1", + * "label": "testExample1", + * "enabled": true, + * "checkable": true, + * "checked": false, + * "icon": "./image/small_16X16.png" + * }, + * { + * "id": "menuItemId2", + * "label": "testExample2", + * "menu": [ + * { + * "id": "menuItemId2-1", + * "label": "testExample2-1", + * "menu": [ + * { + * "id": "menuItemId2-1-1", + * "label": "testExample2-1-1", + * "enabled": false, + * "checkable": true, + * "checked": true + * } + * ] + * }, + * { + * "id": "menuItemId2-2", + * "label": "testExample2-2", + * "enabled": true, + * "checkable": true, + * "checked": true + * } + * ] + * }, + * { + * "label": "---" + * }, + * { + * "id": "menuItemId3", + * "label": "testExample3", + * "enabled": false, + * "checkable": true, + * "checked": false + * } + * ] + * } + * + */ +CSInterface.prototype.setContextMenuByJSON = function(menu, callback) +{ + if ("string" != typeof menu) + { + return; + } + + window.__adobe_cep__.invokeAsync("setContextMenuByJSON", menu, callback); +}; + +/** + * Updates a context menu item by setting the enabled and selection status. + * + * Since 5.2.0 + * + * @param menuItemID The menu item ID. + * @param enabled True to enable the item, false to disable it (gray it out). + * @param checked True to select the item, false to deselect it. + */ +CSInterface.prototype.updateContextMenuItem = function(menuItemID, enabled, checked) +{ + var itemStatus = new ContextMenuItemStatus(menuItemID, enabled, checked); + ret = window.__adobe_cep__.invokeSync("updateContextMenuItem", JSON.stringify(itemStatus)); +}; + +/** + * Get the visibility status of an extension window. + * + * Since 6.0.0 + * + * @return true if the extension window is visible; false if the extension window is hidden. + */ +CSInterface.prototype.isWindowVisible = function() +{ + return window.__adobe_cep__.invokeSync("isWindowVisible", ""); +}; + +/** + * Resize extension's content to the specified dimensions. + * 1. Works with modal and modeless extensions in all Adobe products. + * 2. Extension's manifest min/max size constraints apply and take precedence. + * 3. For panel extensions + * 3.1 This works in all Adobe products except: + * * Premiere Pro + * * Prelude + * * After Effects + * 3.2 When the panel is in certain states (especially when being docked), + * it will not change to the desired dimensions even when the + * specified size satisfies min/max constraints. + * + * Since 6.0.0 + * + * @param width The new width + * @param height The new height + */ +CSInterface.prototype.resizeContent = function(width, height) +{ + window.__adobe_cep__.resizeContent(width, height); +}; + +/** + * Register the invalid certificate callback for an extension. + * This callback will be triggered when the extension tries to access the web site that contains the invalid certificate on the main frame. + * But if the extension does not call this function and tries to access the web site containing the invalid certificate, a default error page will be shown. + * + * Since 6.1.0 + * + * @param callback the callback function + */ +CSInterface.prototype.registerInvalidCertificateCallback = function(callback) +{ + return window.__adobe_cep__.registerInvalidCertificateCallback(callback); +}; + +/** + * Register an interest in some key events to prevent them from being sent to the host application. + * + * This function works with modeless extensions and panel extensions. + * Generally all the key events will be sent to the host application for these two extensions if the current focused element + * is not text input or dropdown, + * If you want to intercept some key events and want them to be handled in the extension, please call this function + * in advance to prevent them being sent to the host application. + * + * Since 6.1.0 + * + * @param keyEventsInterest A JSON string describing those key events you are interested in. A null object or + an empty string will lead to removing the interest + * + * This JSON string should be an array, each object has following keys: + * + * keyCode: [Required] represents an OS system dependent virtual key code identifying + * the unmodified value of the pressed key. + * ctrlKey: [optional] a Boolean that indicates if the control key was pressed (true) or not (false) when the event occurred. + * altKey: [optional] a Boolean that indicates if the alt key was pressed (true) or not (false) when the event occurred. + * shiftKey: [optional] a Boolean that indicates if the shift key was pressed (true) or not (false) when the event occurred. + * metaKey: [optional] (Mac Only) a Boolean that indicates if the Meta key was pressed (true) or not (false) when the event occurred. + * On Macintosh keyboards, this is the command key. To detect Windows key on Windows, please use keyCode instead. + * An example JSON string: + * + * [ + * { + * "keyCode": 48 + * }, + * { + * "keyCode": 123, + * "ctrlKey": true + * }, + * { + * "keyCode": 123, + * "ctrlKey": true, + * "metaKey": true + * } + * ] + * + */ +CSInterface.prototype.registerKeyEventsInterest = function(keyEventsInterest) +{ + return window.__adobe_cep__.registerKeyEventsInterest(keyEventsInterest); +}; + +/** + * Set the title of the extension window. + * This function works with modal and modeless extensions in all Adobe products, and panel extensions in Photoshop, InDesign, InCopy, Illustrator, Flash Pro and Dreamweaver. + * + * Since 6.1.0 + * + * @param title The window title. + */ +CSInterface.prototype.setWindowTitle = function(title) +{ + window.__adobe_cep__.invokeSync("setWindowTitle", title); +}; + +/** + * Get the title of the extension window. + * This function works with modal and modeless extensions in all Adobe products, and panel extensions in Photoshop, InDesign, InCopy, Illustrator, Flash Pro and Dreamweaver. + * + * Since 6.1.0 + * + * @return The window title. + */ +CSInterface.prototype.getWindowTitle = function() +{ + return window.__adobe_cep__.invokeSync("getWindowTitle", ""); +}; diff --git a/openpype/hosts/aftereffects/api/extension/js/libs/jquery-2.0.2.min.js b/openpype/hosts/aftereffects/api/extension/js/libs/jquery-2.0.2.min.js new file mode 100644 index 0000000000..73e5218d21 --- /dev/null +++ b/openpype/hosts/aftereffects/api/extension/js/libs/jquery-2.0.2.min.js @@ -0,0 +1,6 @@ +/*! jQuery v2.0.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery-2.0.2.min.map +*/ +(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.2",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=at(),k=at(),N=at(),E=!1,S=function(){return 0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],H=L.pop,q=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){q.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=vt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+xt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return St(e.replace(z,"$1"),t,r,i)}function st(e){return Q.test(e+"")}function at(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function ut(e){return e[v]=!0,e}function lt(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t,n){e=e.split("|");var r,o=e.length,s=n?null:t;while(o--)(r=i.attrHandle[e[o]])&&r!==t||(i.attrHandle[e[o]]=s)}function pt(e,t){var n=e.getAttributeNode(t);return n&&n.specified?n.value:e[t]===!0?t.toLowerCase():null}function ft(e,t){return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function ht(e){return"input"===e.nodeName.toLowerCase()?e.defaultValue:undefined}function dt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function gt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function mt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function yt(e){return ut(function(t){return t=+t,ut(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.parentWindow;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.frameElement&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=lt(function(e){return e.innerHTML="",ct("type|href|height|width",ft,"#"===e.firstChild.getAttribute("href")),ct(R,pt,null==e.getAttribute("disabled")),e.className="i",!e.getAttribute("className")}),n.input=lt(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}),ct("value",ht,n.attributes&&n.input),n.getElementsByTagName=lt(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=lt(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=lt(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=st(t.querySelectorAll))&&(lt(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),lt(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=st(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&<(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=st(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},n.sortDetached=lt(function(e){return 1&e.compareDocumentPosition(t.createElement("div"))}),S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return dt(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?dt(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:ut,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=vt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ut(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ut(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?ut(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ut(function(e){return function(t){return ot(e,t).length>0}}),contains:ut(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ut(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:yt(function(){return[0]}),last:yt(function(e,t){return[t-1]}),eq:yt(function(e,t,n){return[0>n?n+t:n]}),even:yt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:yt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:yt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:yt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=gt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=mt(t);function vt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function xt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function bt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function wt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function Tt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function Ct(e,t,n,r,i,o){return r&&!r[v]&&(r=Ct(r)),i&&!i[v]&&(i=Ct(i,o)),ut(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Et(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:Tt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=Tt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=Tt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function kt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=bt(function(e){return e===t},a,!0),p=bt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[bt(wt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return Ct(l>1&&wt(f),l>1&&xt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&kt(e.slice(l,r)),o>r&&kt(e=e.slice(r)),o>r&&xt(e))}f.push(n)}return wt(f)}function Nt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=H.call(f));y=Tt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?ut(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=vt(e)),n=t.length;while(n--)o=kt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Nt(i,r))}return o};function Et(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function St(e,t,r,o){var s,u,l,c,p,f=vt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&xt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}i.pseudos.nth=i.pseudos.eq;function jt(){}jt.prototype=i.filters=i.pseudos,i.setFilters=new jt,n.sortStable=v.split("").sort(S).join("")===v,c(),[0,0].sort(S),n.detectDuplicates=E,x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!a||n&&!u||(r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,H,q=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){return t===undefined||t&&"string"==typeof t&&n===undefined?this.get(e,t):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,H=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||H.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return H.access(e,t,n)},_removeData:function(e,t){H.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!H.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));H.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:q.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=H.get(e,t),n&&(!r||x.isArray(n)?r=H.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire() +},_queueHooks:function(e,t){var n=t+"queueHooks";return H.get(e,n)||H.access(e,n,{empty:x.Callbacks("once memory").add(function(){H.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=H.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,i="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,s=0,a=x(this),u=t,l=e.match(w)||[];while(o=l[s++])u=i?u:!a.hasClass(o),a[u?"addClass":"removeClass"](o)}else(n===r||"boolean"===n)&&(this.className&&H.set(this,"__className__",this.className),this.className=this.className||e===!1?"":H.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=H.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=H.hasData(e)&&H.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,H.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(H.get(a,"events")||{})[t.type]&&H.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(H.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*\s*$/g,ct={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!H.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1>")+a[2],l=a[0];while(l--)o=o.firstChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[H.expando],o&&(t=H.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);H.cache[o]&&delete H.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)H.set(e[r],"globalEval",!t||H.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(H.hasData(e)&&(o=H.access(e),s=H.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function Ht(t){return e.getComputedStyle(t,null)}function qt(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=H.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=H.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&H.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=Ht(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return qt(this,!0)},hide:function(){return qt(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:Lt(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||Ht(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Ht(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("