diff --git a/pype.py b/pype.py index e90c257595..992e0c35ba 100644 --- a/pype.py +++ b/pype.py @@ -48,8 +48,11 @@ from igniter.tools import load_environments, add_acre_to_sys_path from igniter import BootstrapRepos -add_acre_to_sys_path() -import acre +try: + import acre +except ImportError: + add_acre_to_sys_path() + import acre def set_environments() -> None: @@ -101,18 +104,9 @@ def set_modules_environments(): def boot(): """Bootstrap Pype.""" - art = r""" - ____________ - /\ ___ \ - \ \ \/_\ \ - \ \ _____/ ___ ___ ___ - \ \ \___/ ---- \ \\ \\ \ - \ \____\ / \____\ \__\\__\\__\ - \/____/ \/____/ . PYPE Club . - """ - - print(art) + from pype.lib.terminal_splash import play_animation + play_animation() # find pype versions bootstrap = BootstrapRepos() diff --git a/pype/lib/splash.txt b/pype/lib/splash.txt new file mode 100644 index 0000000000..833bcd4b9c --- /dev/null +++ b/pype/lib/splash.txt @@ -0,0 +1,413 @@ + + + + * + + + + + + + .* + + + + + + * + .* + * + + + + . + * + .* + * + . + + . + * + .* + .* + .* + * + . + . + * + .* + .* + .* + * + . + _. + /** + \ * + \* + * + * + . + __. + ---* + \ \* + \ * + \* + * + . + \___. + /* * + \ \ * + \ \* + \ * + \* + . + |____. + /* * + \|\ * + \ \ * + \ \ * + \ \* + \/. + _/_____. + /* * + / \ * + \ \ * + \ \ * + \ \__* + \/__. + __________. + --*-- ___* + \ \ \/_* + \ \ __* + \ \ \_* + \ \____\* + \/____/. + \____________ . + /* ___ \* + \ \ \/_\ * + \ \ _____* + \ \ \___/* + \ \____\ * + \/____/ . + |___________ . + /* ___ \ * + \|\ \/_\ \ * + \ \ _____/ * + \ \ \___/ * + \ \____\ / * + \/____/ \. + _/__________ . + /* ___ \ * + / \ \/_\ \ * + \ \ _____/ * + \ \ \___/ ---* + \ \____\ / \__* + \/____/ \/__. + ____________ . + --*-- ___ \ * + \ \ \/_\ \ * + \ \ _____/ * + \ \ \___/ ---- * + \ \____\ / \____\* + \/____/ \/____/. + ____________ + /\ ___ \ . + \ \ \/_\ \ * + \ \ _____/ * + \ \ \___/ ---- * + \ \____\ / \____\ . + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ . + \ \ _____/ * + \ \ \___/ ---- * + \ \____\ / \____\ . + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ . + \ \ \___/ ---- * + \ \____\ / \____\ . + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ + \ \ \___/ ---- * + \ \____\ / \____\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ + \ \ \___/ ---- . + \ \____\ / \____\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ _ + \ \ \___/ ---- + \ \____\ / \____\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- + \ \____\ / \____\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- \ + \ \____\ / \____\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- \ + \ \____\ / \____\ \ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- \ + \ \____\ / \____\ __\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- \ + \ \____\ / \____\ \__\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- \ \ + \ \____\ / \____\ \__\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ + \ \ \___/ ---- \ \ + \ \____\ / \____\ \__\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___. + \ \ \___/ ---- \ \\ + \ \____\ / \____\ \__\, + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ . + \ \ \___/ ---- \ \\ + \ \____\ / \____\ \__\\, + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ _. + \ \ \___/ ---- \ \\\ + \ \____\ / \____\ \__\\\ + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ __. + \ \ \___/ ---- \ \\ \ + \ \____\ / \____\ \__\\_/. + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___. + \ \ \___/ ---- \ \\ \\ + \ \____\ / \____\ \__\\__\. + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ . + \ \ \___/ ---- \ \\ \\ + \ \____\ / \____\ \__\\__\\. + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ _. + \ \ \___/ ---- \ \\ \\\ + \ \____\ / \____\ \__\\__\\. + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ __. + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\_. + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ __. + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__. + \/____/ \/____/ + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ * + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ O* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ .oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ ..oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . .oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . p.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . Py.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYp.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPe.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE .oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE c.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE C1.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE ClU.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE CluB.oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE Club .oO* + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE Club . .. + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE Club . .. + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE Club . . + ____________ + /\ ___ \ + \ \ \/_\ \ + \ \ _____/ ___ ___ ___ + \ \ \___/ ---- \ \\ \\ \ + \ \____\ / \____\ \__\\__\\__\ + \/____/ \/____/ . PYPE Club . diff --git a/pype/lib/terminal.py b/pype/lib/terminal.py index 61d074f474..afaca8241a 100644 --- a/pype/lib/terminal.py +++ b/pype/lib/terminal.py @@ -13,11 +13,10 @@ import re import os import sys -noColorama = False -try: - from colorama import Fore, Style, init, ansitowin32 -except ImportError: - noColorama = True +import blessed + + +term = blessed.Terminal() class Terminal: @@ -30,40 +29,37 @@ class Terminal: """ # shortcuts for colorama codes - if noColorama: - _SB = _RST = _LR = _LG = _LB = _LM = _R = _G = _B = _C = _Y = _W = "" - _LY = "" - else: - _SB = Style.BRIGHT - _RST = Style.RESET_ALL - _LR = Fore.LIGHTRED_EX - _LG = Fore.LIGHTGREEN_EX - _LB = Fore.LIGHTBLUE_EX - _LM = Fore.LIGHTMAGENTA_EX - _LY = Fore.LIGHTYELLOW_EX - _R = Fore.RED - _G = Fore.GREEN - _B = Fore.BLUE - _C = Fore.CYAN - _Y = Fore.YELLOW - _W = Fore.WHITE + + _SB = term.bold + _RST = "" + _LR = term.tomato2 + _LG = term.aquamarine3 + _LB = term.turquoise2 + _LM = term.slateblue2 + _LY = term.gold + _R = term.red + _G = term.green + _B = term.blue + _C = term.cyan + _Y = term.yellow + _W = term.white # dictionary replacing string sequences with colorized one _sdict = { - r">>> ": _SB + _G + r">>> " + _RST, + r">>> ": _SB + _LG + r">>> " + _RST, r"!!!(?!\sCRI|\sERR)": _SB + _R + r"!!! " + _RST, r"\-\-\- ": _SB + _C + r"--- " + _RST, - r"\*\*\*(?!\sWRN)": _SB + _LM + r"***" + _RST, + r"\*\*\*(?!\sWRN)": _SB + _LY + r"***" + _RST, r"\*\*\* WRN": _SB + _LY + r"*** WRN" + _RST, - r" \- ": _SB + _Y + r" - " + _RST, + r" \- ": _SB + _LY + r" - " + _RST, r"\[ ": _SB + _LG + r"[ " + _RST, r"\]": _SB + _LG + r"]" + _RST, r"{": _LG + r"{", r"}": r"}" + _RST, r"\(": _LY + r"(", r"\)": r")" + _RST, - r"^\.\.\. ": _SB + _LM + r"... " + _RST, + r"^\.\.\. ": _SB + _LR + r"... " + _RST, r"!!! ERR: ": _SB + _LR + r"!!! ERR: " + _RST, r"!!! CRI: ": @@ -73,8 +69,7 @@ class Terminal: } def __init__(self): - if not noColorama: - init() + pass @staticmethod def _multiple_replace(text, adict): @@ -108,11 +103,6 @@ class Terminal: str: Colorized message. """ - if noColorama: - print(message) - return message - if not isinstance(sys.stdout, ansitowin32.StreamWrapper): - init() colorized = Terminal.log(message) print(colorized) diff --git a/pype/lib/terminal_splash.py b/pype/lib/terminal_splash.py new file mode 100644 index 0000000000..7a94f2243e --- /dev/null +++ b/pype/lib/terminal_splash.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +"""Pype terminal animation.""" +import blessed +from pathlib import Path +from time import sleep + + +term = blessed.Terminal() + + +def play_animation(): + """Play ASCII art Pype animation.""" + print(term.home + term.clear) + frame_size = 7 + splash_file = Path(__file__).parent / "splash.txt" + with splash_file.open("r") as sf: + animation = sf.readlines() + + animation_length = int(len(animation) / frame_size) + current_frame = 0 + for _ in range(animation_length): + frame = "" + y = 0 + for scanline in animation[current_frame:current_frame + frame_size]: + frame += scanline + y += 1 + + with term.location(0, 0): + # term.aquamarine3_bold(frame) + print(f"{term.bold}{term.aquamarine3}{frame}{term.normal}") + + sleep(0.02) + current_frame += frame_size + print(term.move_y(7)) diff --git a/pype/modules/ftrack/events/event_version_to_task_statuses.py b/pype/modules/ftrack/events/event_version_to_task_statuses.py index 0ea72be1cb..ed47d2f8a9 100644 --- a/pype/modules/ftrack/events/event_version_to_task_statuses.py +++ b/pype/modules/ftrack/events/event_version_to_task_statuses.py @@ -1,25 +1,40 @@ from pype.modules.ftrack import BaseEvent -from pype.api import get_project_settings class VersionToTaskStatus(BaseEvent): + """Propagates status from version to task when changed.""" def launch(self, session, event): - '''Propagates status from version to task when changed''' + # Filter event entities + # - output is dictionary where key is project id and event info in + # value + filtered_entities_info = self.filter_entity_info(event) + if not filtered_entities_info: + return - # start of event procedure ---------------------------------- - for entity in event['data'].get('entities', []): + for project_id, entities_info in filtered_entities_info.items(): + self.process_by_project(session, event, project_id, entities_info) + + # TODO remove `join_query_keys` as it should be in `BaseHandler` + @staticmethod + def join_query_keys(keys): + """Helper to join keys to query.""" + return ",".join(["\"{}\"".format(key) for key in keys]) + + def filter_entity_info(self, event): + filtered_entity_info = {} + for entity_info in event["data"].get("entities", []): # Filter AssetVersions - if entity["entityType"] != "assetversion": + if entity_info["entityType"] != "assetversion": continue # Skip if statusid not in keys (in changes) - keys = entity.get("keys") + keys = entity_info.get("keys") if not keys or "statusid" not in keys: continue # Get new version task name version_status_id = ( - entity + entity_info .get("changes", {}) .get("statusid", {}) .get("new", {}) @@ -29,74 +44,162 @@ class VersionToTaskStatus(BaseEvent): if not version_status_id: continue - try: - version_status = session.get("Status", version_status_id) - except Exception: - self.log.warning( - "Troubles with query status id [ {} ]".format( - version_status_id - ), - exc_info=True + # Get project id from entity info + project_id = entity_info["parents"][-1]["entityId"] + if project_id not in filtered_entity_info: + filtered_entity_info[project_id] = [] + filtered_entity_info[project_id].append(entity_info) + return filtered_entity_info + + def process_by_project(self, session, event, project_id, entities_info): + # Check for project data if event is enabled for event handler + status_mapping = None + project_entity = self.get_project_entity_from_event( + session, event, project_id + ) + project_settings = self.get_settings_for_project( + session, event, project_entity=project_entity + ) + + project_name = project_entity["full_name"] + # Load status mapping from presets + event_settings = ( + project_settings["ftrack"]["events"]["status_version_to_task"] + ) + # Skip if event is not enabled or status mapping is not set + if not event_settings["enabled"]: + self.log.debug("Project \"{}\" has disabled {}".format( + project_name, self.__class__.__name__ + )) + return + + _status_mapping = event_settings["mapping"] + if not _status_mapping: + self.log.debug( + "Project \"{}\" does not have set mapping for {}".format( + project_name, self.__class__.__name__ ) + ) + return - if not version_status: + status_mapping = { + key.lower(): value + for key, value in _status_mapping.items() + } + + asset_types_to_skip = [ + short_name.lower() + for short_name in event_settings["asset_types_to_skip"] + ] + + # Collect entity ids + asset_version_ids = set() + for entity_info in entities_info: + asset_version_ids.add(entity_info["entityId"]) + + # Query tasks for AssetVersions + _asset_version_entities = session.query( + "AssetVersion where task_id != none and id in ({})".format( + self.join_query_keys(asset_version_ids) + ) + ).all() + if not _asset_version_entities: + return + + # Filter asset versions by asset type and store their task_ids + task_ids = set() + asset_version_entities = [] + for asset_version in _asset_version_entities: + if asset_types_to_skip: + short_name = asset_version["asset"]["type"]["short"].lower() + if short_name in asset_types_to_skip: + continue + asset_version_entities.append(asset_version) + task_ids.add(asset_version["task_id"]) + + # Skipt if `task_ids` are empty + if not task_ids: + return + + task_entities = session.query( + "select link from Task where id in ({})".format( + self.join_query_keys(task_ids) + ) + ).all() + task_entities_by_id = { + task_entiy["id"]: task_entiy + for task_entiy in task_entities + } + + # Prepare asset version by their id + asset_versions_by_id = { + asset_version["id"]: asset_version + for asset_version in asset_version_entities + } + + # Query status entities + status_ids = set() + for entity_info in entities_info: + # Skip statuses of asset versions without task + if entity_info["entityId"] not in asset_versions_by_id: continue + status_ids.add(entity_info["changes"]["statusid"]["new"]) - version_status_orig = version_status["name"] + version_status_entities = session.query( + "select id, name from Status where id in ({})".format( + self.join_query_keys(status_ids) + ) + ).all() - # Get entities necessary for processing - version = session.get("AssetVersion", entity["entityId"]) - task = version.get("task") - if not task: - continue - - project_entity = self.get_project_from_entity(task) - project_name = project_entity["full_name"] - project_settings = get_project_settings(project_name) - - # Load status mapping from presets - status_mapping = ( - project_settings["ftrack"]["events"]["status_version_to_task"]) - # Skip if mapping is empty - if not status_mapping: + # Qeury statuses + statusese_by_obj_id = self.statuses_for_tasks( + session, task_entities, project_entity + ) + # Prepare status names by their ids + status_name_by_id = { + status_entity["id"]: status_entity["name"] + for status_entity in version_status_entities + } + for entity_info in entities_info: + entity_id = entity_info["entityId"] + status_id = entity_info["changes"]["statusid"]["new"] + status_name = status_name_by_id.get(status_id) + if not status_name: continue + status_name_low = status_name.lower() # Lower version status name and check if has mapping - version_status = version_status_orig.lower() new_status_names = [] - mapped = status_mapping.get(version_status) + mapped = status_mapping.get(status_name_low) if mapped: new_status_names.extend(list(mapped)) - new_status_names.append(version_status) + new_status_names.append(status_name_low) self.log.debug( "Processing AssetVersion status change: [ {} ]".format( - version_status_orig + status_name ) ) + asset_version = asset_versions_by_id[entity_id] + task_entity = task_entities_by_id[asset_version["task_id"]] + type_id = task_entity["type_id"] + # Lower all names from presets new_status_names = [name.lower() for name in new_status_names] - - if version["asset"]["type"]["short"].lower() == "scene": - continue - - project_schema = project_entity["project_schema"] - # Get all available statuses for Task - statuses = project_schema.get_statuses("Task", task["type_id"]) - # map lowered status name with it's object - stat_names_low = { - status["name"].lower(): status for status in statuses - } + task_statuses_by_low_name = statusese_by_obj_id[type_id] new_status = None for status_name in new_status_names: - if status_name not in stat_names_low: + if status_name not in task_statuses_by_low_name: + self.log.debug(( + "Task does not have status name \"{}\" available." + ).format(status_name)) continue # store object of found status - new_status = stat_names_low[status_name] + new_status = task_statuses_by_low_name[status_name] self.log.debug("Status to set: [ {} ]".format( new_status["name"] )) @@ -110,16 +213,15 @@ class VersionToTaskStatus(BaseEvent): ) ) continue - # Get full path to task for logging - ent_path = "/".join([ent["name"] for ent in task["link"]]) + ent_path = "/".join([ent["name"] for ent in task_entity["link"]]) # Setting task status try: - task["status"] = new_status + task_entity["status"] = new_status session.commit() self.log.debug("[ {} ] Status updated to [ {} ]".format( - ent_path, new_status['name'] + ent_path, new_status["name"] )) except Exception: session.rollback() @@ -128,6 +230,22 @@ class VersionToTaskStatus(BaseEvent): exc_info=True ) + def statuses_for_tasks(self, session, task_entities, project_entity): + task_type_ids = set() + for task_entity in task_entities: + task_type_ids.add(task_entity["type_id"]) + + project_schema = project_entity["project_schema"] + output = {} + for task_type_id in task_type_ids: + statuses = project_schema.get_statuses("Task", task_type_id) + output[task_type_id] = { + status["name"].lower(): status + for status in statuses + } + + return output + def register(session, plugins_presets): '''Register plugin. Called when used as an plugin.''' diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index dbf2a2dc10..96581f0a38 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -23,7 +23,7 @@ from pype.modules.ftrack.ftrack_server.lib import ( get_ftrack_event_mongo_info ) -import socket_thread +from pype.modules.ftrack.ftrack_server import socket_thread class MongoPermissionsError(Exception): diff --git a/pype/modules/ftrack/ftrack_server/ftrack_server.py b/pype/modules/ftrack/ftrack_server/ftrack_server.py index af48bfadc8..93c7cd3a67 100644 --- a/pype/modules/ftrack/ftrack_server/ftrack_server.py +++ b/pype/modules/ftrack/ftrack_server/ftrack_server.py @@ -8,10 +8,10 @@ import inspect import ftrack_api -from pype.api import Logger +from pype.lib import PypeLogger -log = Logger().get_logger(__name__) +log = PypeLogger().get_logger(__name__) """ # Required - Needed for connection to Ftrack diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/pype/modules/ftrack/lib/ftrack_base_handler.py index e928f2fb88..669381af90 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/pype/modules/ftrack/lib/ftrack_base_handler.py @@ -1,6 +1,8 @@ import functools import time from pype.api import Logger +from pype.settings import get_project_settings + import ftrack_api from pype.modules.ftrack import ftrack_server @@ -581,3 +583,67 @@ class BaseHandler(object): return self.session.query( "Project where id is {}".format(project_data["id"]) ).one() + + def get_project_entity_from_event(self, session, event, project_id): + """Load or query and fill project entity from/to event data. + + Project data are stored by ftrack id because in most cases it is + easier to access project id than project name. + + Args: + session (ftrack_api.Session): Current session. + event (ftrack_api.Event): Processed event by session. + project_id (str): Ftrack project id. + """ + if not project_id: + raise ValueError( + "Entered `project_id` is not valid. {} ({})".format( + str(project_id), str(type(project_id)) + ) + ) + # Try to get project entity from event + project_entities = event["data"].get("project_entities") + if not project_entities: + project_entities = {} + event["data"]["project_entities"] = project_entities + + project_entity = project_entities.get(project_id) + if not project_entity: + # Get project entity from task and store to event + project_entity = session.get("Project", project_id) + event["data"]["project_entities"][project_id] = project_entity + return project_entity + + def get_settings_for_project( + self, session, event, project_id=None, project_entity=None + ): + """Load or fill pype's project settings from event data. + + Project data are stored by ftrack id because in most cases it is + easier to access project id than project name. + + Args: + session (ftrack_api.Session): Current session. + event (ftrack_api.Event): Processed event by session. + project_id (str): Ftrack project id. Must be entered if + project_entity is not. + project_entity (ftrack_api.Entity): Project entity. Must be entered + if project_id is not. + """ + if not project_entity: + project_entity = self.get_project_entity_from_event( + session, event, project_id + ) + + project_name = project_entity["full_name"] + + project_settings_by_id = event["data"].get("project_settings") + if not project_settings_by_id: + project_settings_by_id = {} + event["data"]["project_settings"] = project_settings_by_id + + project_settings = project_settings_by_id.get(project_id) + if not project_settings: + project_settings = get_project_settings(project_name) + event["data"]["project_settings"][project_id] = project_settings + return project_settings diff --git a/pype/pype_commands.py b/pype/pype_commands.py index cc54deb2ee..f504728ca1 100644 --- a/pype/pype_commands.py +++ b/pype/pype_commands.py @@ -60,7 +60,18 @@ class PypeCommands: return return_code def launch_eventservercli(self, args): - pass + from pype.modules import ftrack + from pype.lib import execute + + fname = os.path.join( + os.path.dirname(os.path.abspath(ftrack.__file__)), + "ftrack_server", + "event_server_cli.py" + ) + + return execute([ + sys.executable, "-u", fname + ]) def publish(self, gui, paths): pass diff --git a/pype/settings/defaults/project_settings/ftrack.json b/pype/settings/defaults/project_settings/ftrack.json index 5481574ef8..8a597c3e6a 100644 --- a/pype/settings/defaults/project_settings/ftrack.json +++ b/pype/settings/defaults/project_settings/ftrack.json @@ -71,11 +71,13 @@ "status_version_to_task": { "enabled": true, "mapping": { - "Complete": [ - "Approved", + "Approved": [ "Complete" ] - } + }, + "asset_types_to_skip": [ + "scene" + ] }, "first_version_status": { "enabled": true, diff --git a/pype/settings/defaults/project_settings/global.json b/pype/settings/defaults/project_settings/global.json index da56fd34e7..4e2b9fce81 100644 --- a/pype/settings/defaults/project_settings/global.json +++ b/pype/settings/defaults/project_settings/global.json @@ -179,4 +179,4 @@ } } } -} +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/maya.json b/pype/settings/defaults/project_settings/maya.json index c779d495c4..b8c0dffa26 100644 --- a/pype/settings/defaults/project_settings/maya.json +++ b/pype/settings/defaults/project_settings/maya.json @@ -136,7 +136,8 @@ "enabled": false }, "ValidateAttributes": { - "enabled": false + "enabled": false, + "attributes": {} }, "ExtractCameraAlembic": { "enabled": true, diff --git a/pype/settings/defaults/project_settings/nuke.json b/pype/settings/defaults/project_settings/nuke.json index 61001914d2..82dcf23694 100644 --- a/pype/settings/defaults/project_settings/nuke.json +++ b/pype/settings/defaults/project_settings/nuke.json @@ -87,4 +87,4 @@ ] }, "filters": {} -} +} \ No newline at end of file diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json index 4c3e30b77f..480e84ea39 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json @@ -91,40 +91,82 @@ ] }, { - "type": "dict", - "key": "thumbnail_updates", - "label": "Update Hierarchy thumbnails", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "label", - "label": "Push thumbnail from version, up through multiple hierarchy levels." - }, - { - "type": "number", - "key": "levels", - "label": "Levels" - } - ] + "type": "list", + "object_type": "text" + } + }] + }, + { + "type": "dict", + "key": "status_version_to_task", + "label": "Sync status from Version to Task", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" }, { - "type": "dict", - "key": "user_assignment", - "label": "Run script on user assignments", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] + "type": "label", + "label": "Change Task status based on a changed Version status.
Version's new status on the left will trigger a change of a task status to the first available from the list on right.
- if no status from the list is available it will use the same status as the version." }, + { + "type": "dict-modifiable", + "key": "mapping", + "object_type": + { + "type": "list", + "object_type": "text" + } + }, + { + "type": "separator" + }, + { + "type": "label", + "label": "Disable event if status was changed on specific Asset type." + }, + { + "type": "list", + "label": "Asset types (short)", + "key": "asset_types_to_skip", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "key": "first_version_status", + "label": "Set status on first created version", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "status", + "label": "Status" + }] + }, + { + "type": "dict", + "key": "next_task_update", + "label": "Update status on next task", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "key": "mapping", + "object_type": { "type": "dict", "key": "status_update", diff --git a/requirements.txt b/requirements.txt index ac197b9b1e..24cf4f31c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ aiohttp appdirs arrow +blessed certifi Click clique==1.5.0