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