diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py
index d0f45c36b5b..f9b5614dd63 100644
--- a/openpype/hosts/houdini/api/pipeline.py
+++ b/openpype/hosts/houdini/api/pipeline.py
@@ -186,6 +186,7 @@ def on_file_event_callback(event):
emit_event("open")
elif event == hou.hipFileEventType.AfterSave:
emit_event("save")
+ emit_event("after.save")
elif event == hou.hipFileEventType.BeforeSave:
emit_event("before.save")
elif event == hou.hipFileEventType.AfterClear:
diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py
index cdefd05c114..b4d642c8ad6 100644
--- a/openpype/hosts/nuke/api/lib.py
+++ b/openpype/hosts/nuke/api/lib.py
@@ -2600,12 +2600,14 @@ def make_format_string(self, **kwargs):
)
def set_context_settings(self):
+ os.environ["OP_NUKE_SKIP_SAVE_EVENT"] = "True"
# replace reset resolution from avalon core to pype's
self.reset_resolution()
# replace reset resolution from avalon core to pype's
self.reset_frame_range_handles()
# add colorspace menu item
self.set_colorspace()
+ del os.environ["OP_NUKE_SKIP_SAVE_EVENT"]
def set_favorites(self):
from .utils import set_context_favorites
diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py
index 2cff55030d1..f59ea64100d 100644
--- a/openpype/hosts/nuke/api/pipeline.py
+++ b/openpype/hosts/nuke/api/pipeline.py
@@ -14,7 +14,7 @@
IPublishHost
)
from openpype.settings import get_current_project_settings
-from openpype.lib import register_event_callback, Logger
+from openpype.lib import register_event_callback, emit_event, Logger
from openpype.pipeline import (
register_loader_plugin_path,
register_creator_plugin_path,
@@ -32,7 +32,6 @@
ROOT_DATA_KNOB,
INSTANCE_DATA_KNOB,
get_main_window,
- add_publish_knob,
WorkfileSettings,
# TODO: remove this once workfile builder will be removed
process_workfile_builder,
@@ -178,6 +177,10 @@ def add_nuke_callbacks():
# set apply all workfile settings on script load and save
nuke.addOnScriptLoad(WorkfileSettings().set_context_settings)
+ # Emit events
+ nuke.addOnCreate(_on_scene_open, nodeClass="Root")
+ nuke.addOnScriptSave(_on_scene_save)
+
if nuke_settings["nuke-dirmap"]["enabled"]:
log.info("Added Nuke's dir-mapping callback ...")
@@ -636,3 +639,14 @@ def select_instance(instance):
"""
instance_node = instance.transient_data["node"]
instance_node["selected"].setValue(True)
+
+
+def _on_scene_open(*args):
+ emit_event("open")
+
+
+def _on_scene_save(*args):
+ skip = os.getenv("OP_NUKE_SKIP_SAVE_EVENT")
+ if skip:
+ return
+ emit_event("after.save")
diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py
index 2042367a7e0..44031b530db 100644
--- a/openpype/modules/ftrack/ftrack_module.py
+++ b/openpype/modules/ftrack/ftrack_module.py
@@ -3,6 +3,7 @@
import collections
import platform
+from openpype.lib import register_event_callback
from openpype.modules import (
click_wrap,
OpenPypeModule,
@@ -10,9 +11,15 @@
IPluginPaths,
ISettingsChangeListener
)
-from openpype.settings import SaveWarningExc
+from openpype.settings import SaveWarningExc, get_project_settings
from openpype.lib import Logger
+from openpype.pipeline import (
+ get_current_project_name,
+ get_current_asset_name,
+ get_current_task_name
+)
+
FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
_URL_NOT_SET = object()
@@ -62,6 +69,133 @@ def initialize(self, settings):
self.timers_manager_connector = None
self._timers_manager_module = None
+ # Hooks when a file has been opened or saved
+ register_event_callback("open", self.after_file_open)
+ register_event_callback("after.save", self.after_file_save)
+
+ def find_ftrack_task_entity(
+ self, session, project_name, asset_name, task_name
+ ):
+ project_entity = session.query(
+ "Project where full_name is \"{}\"".format(project_name)
+ ).first()
+ if not project_entity:
+ self.log.warning(
+ "Couldn't find project \"{}\" in Ftrack.".format(project_name)
+ )
+ return
+
+ potential_task_entities = session.query((
+ "TypedContext where parent.name is \"{}\" and project_id is \"{}\""
+ ).format(asset_name, project_entity["id"])).all()
+ filtered_entities = []
+ for _entity in potential_task_entities:
+ if (
+ _entity.entity_type.lower() == "task"
+ and _entity["name"] == task_name
+ ):
+ filtered_entities.append(_entity)
+
+ if not filtered_entities:
+ self.log.warning((
+ "Couldn't find task \"{}\" under parent \"{}\" in Ftrack."
+ ).format(task_name, asset_name))
+ return
+
+ if len(filtered_entities) > 1:
+ self.log.warning((
+ "Found more than one task \"{}\""
+ " under parent \"{}\" in Ftrack."
+ ).format(task_name, asset_name))
+ return
+
+ return filtered_entities[0]
+
+ def after_file_open(self, event):
+ self._auto_update_task_status("status_change_on_file_open")
+
+ def after_file_save(self, event):
+ self._auto_update_task_status("status_change_on_file_save")
+
+ def _auto_update_task_status(self, setting_mapping_section):
+ # Create ftrack session
+ try:
+ session = self.create_ftrack_session()
+ except Exception: # noqa
+ self.log.warning("Couldn't create ftrack session.", exc_info=True)
+ return
+ # ----------------------
+
+ project_name = get_current_project_name()
+ project_settings = get_project_settings(project_name)
+
+ # Do we want/need/can update the status?
+ change_task_status = project_settings["ftrack"]["application_handlers"]["change_task_status"]
+ if not change_task_status["enabled"]:
+ return
+
+ mapping = change_task_status[setting_mapping_section]
+ if not mapping:
+ # No rules registered, skip.
+ return
+ # --------------------------------------
+
+ asset_name = get_current_asset_name()
+ task_name = get_current_task_name()
+
+ # Find the entity
+ entity = self.find_ftrack_task_entity(session, project_name, asset_name, task_name)
+ if not entity:
+ # No valid entity found, quit.
+ return
+ # ---------------
+
+ ent_path = "/".join([ent["name"] for ent in entity["link"]])
+ actual_status = entity["status"]["name"]
+
+ # Find the next status
+ if "__ignore__" in mapping:
+ ignored_statuses = [status for status in mapping["__ignore__"]]
+ if actual_status in ignored_statuses:
+ # We can exit, the status is flagged to be ignored.
+ return
+
+ # Removing to avoid looping on it
+ mapping.pop("__ignore__")
+
+ next_status = None
+
+ for to_status, from_statuses in mapping.items():
+ from_statuses = [status for status in from_statuses]
+ if "__any__" in from_statuses:
+ next_status = to_status
+ # Not breaking, in case a better mapping is set after.
+ continue
+
+ if actual_status in from_statuses:
+ next_status = to_status
+ # We found a valid mapping (other that __any__) we stop looking.
+ break
+
+ if not next_status:
+ # No valid next status found, skip.
+ return
+ # --------------------
+
+ # Change the status on ftrack
+ try:
+ query = "Status where name is \"{}\"".format(next_status)
+ next_status_obj = session.query(query).one()
+
+ entity["status"] = next_status_obj
+ session.commit()
+ self.log.debug("Changing current task status to \"{}\" <{}>".format(next_status, ent_path))
+ except Exception: # noqa
+ session.rollback()
+ msg = "Status \"{}\" in presets wasn't found on Ftrack entity type \"{}\"".format(next_status,
+ entity.entity_type)
+ self.log.error(msg)
+
def get_ftrack_url(self):
"""Resolved ftrack url.
diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py
deleted file mode 100644
index 1876ff20ebc..00000000000
--- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import os
-
-import ftrack_api
-from openpype.settings import get_project_settings
-from openpype.lib.applications import PostLaunchHook, LaunchTypes
-
-
-class PostFtrackHook(PostLaunchHook):
- order = None
- launch_types = {LaunchTypes.local}
-
- def execute(self):
- project_name = self.data.get("project_name")
- asset_name = self.data.get("asset_name")
- task_name = self.data.get("task_name")
-
- missing_context_keys = set()
- if not project_name:
- missing_context_keys.add("project_name")
- if not asset_name:
- missing_context_keys.add("asset_name")
- if not task_name:
- missing_context_keys.add("task_name")
-
- if missing_context_keys:
- missing_keys_str = ", ".join([
- "\"{}\"".format(key) for key in missing_context_keys
- ])
- self.log.debug("Hook {} skipped. Missing data keys: {}".format(
- self.__class__.__name__, missing_keys_str
- ))
- return
-
- required_keys = ("FTRACK_SERVER", "FTRACK_API_USER", "FTRACK_API_KEY")
- for key in required_keys:
- if not os.environ.get(key):
- self.log.debug((
- "Missing required environment \"{}\""
- " for Ftrack after launch procedure."
- ).format(key))
- return
-
- try:
- session = ftrack_api.Session(auto_connect_event_hub=True)
- self.log.debug("Ftrack session created")
- except Exception:
- self.log.warning("Couldn't create Ftrack session")
- return
-
- try:
- entity = self.find_ftrack_task_entity(
- session, project_name, asset_name, task_name
- )
- if entity:
- self.ftrack_status_change(session, entity, project_name)
-
- except Exception:
- self.log.warning(
- "Couldn't finish Ftrack procedure.", exc_info=True
- )
- return
-
- finally:
- session.close()
-
- def find_ftrack_task_entity(
- self, session, project_name, asset_name, task_name
- ):
- project_entity = session.query(
- "Project where full_name is \"{}\"".format(project_name)
- ).first()
- if not project_entity:
- self.log.warning(
- "Couldn't find project \"{}\" in Ftrack.".format(project_name)
- )
- return
-
- potential_task_entities = session.query((
- "TypedContext where parent.name is \"{}\" and project_id is \"{}\""
- ).format(asset_name, project_entity["id"])).all()
- filtered_entities = []
- for _entity in potential_task_entities:
- if (
- _entity.entity_type.lower() == "task"
- and _entity["name"] == task_name
- ):
- filtered_entities.append(_entity)
-
- if not filtered_entities:
- self.log.warning((
- "Couldn't find task \"{}\" under parent \"{}\" in Ftrack."
- ).format(task_name, asset_name))
- return
-
- if len(filtered_entities) > 1:
- self.log.warning((
- "Found more than one task \"{}\""
- " under parent \"{}\" in Ftrack."
- ).format(task_name, asset_name))
- return
-
- return filtered_entities[0]
-
- def ftrack_status_change(self, session, entity, project_name):
- project_settings = get_project_settings(project_name)
- status_update = project_settings["ftrack"]["events"]["status_update"]
- if not status_update["enabled"]:
- self.log.debug(
- "Status changes are disabled for project \"{}\"".format(
- project_name
- )
- )
- return
-
- status_mapping = status_update["mapping"]
- if not status_mapping:
- self.log.warning(
- "Project \"{}\" does not have set status changes.".format(
- project_name
- )
- )
- return
-
- actual_status = entity["status"]["name"].lower()
- already_tested = set()
- ent_path = "/".join(
- [ent["name"] for ent in entity["link"]]
- )
- while True:
- next_status_name = None
- for key, value in status_mapping.items():
- if key in already_tested:
- continue
-
- value = [i.lower() for i in value]
- if actual_status in value or "__any__" in value:
- if key != "__ignore__":
- next_status_name = key
- already_tested.add(key)
- break
- already_tested.add(key)
-
- if next_status_name is None:
- break
-
- try:
- query = "Status where name is \"{}\"".format(
- next_status_name
- )
- status = session.query(query).one()
-
- entity["status"] = status
- session.commit()
- self.log.debug("Changing status to \"{}\" <{}>".format(
- next_status_name, ent_path
- ))
- break
-
- except Exception:
- session.rollback()
- msg = (
- "Status \"{}\" in presets wasn't found"
- " on Ftrack entity type \"{}\""
- ).format(next_status_name, entity.entity_type)
- self.log.warning(msg)
diff --git a/openpype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py
index 55400c22aba..d499f07ae44 100644
--- a/openpype/modules/ftrack/lib/ftrack_base_handler.py
+++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py
@@ -13,7 +13,7 @@
from openpype_modules.ftrack import ftrack_server
-class MissingPermision(Exception):
+class MissingPermission(Exception):
def __init__(self, message=None):
if message is None:
message = 'Ftrack'
@@ -101,7 +101,7 @@ def wrapper_register(*args, **kwargs):
self.log.info((
'{} "{}" - Registered successfully ({:.4f}sec)'
).format(self.type, label, run_time))
- except MissingPermision as MPE:
+ except MissingPermission as MPE:
self.log.info((
'!{} "{}" - You\'re missing required {} permissions'
).format(self.type, label, str(MPE)))
diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json
index e2ca334b5f3..f7eb9668b8c 100644
--- a/openpype/settings/defaults/project_settings/ftrack.json
+++ b/openpype/settings/defaults/project_settings/ftrack.json
@@ -47,22 +47,6 @@
"user_assignment": {
"enabled": true
},
- "status_update": {
- "enabled": true,
- "mapping": {
- "In Progress": [
- "__any__"
- ],
- "Ready": [
- "Not Ready"
- ],
- "__ignore__": [
- "in progress",
- "omitted",
- "on hold"
- ]
- }
- },
"status_task_to_parent": {
"enabled": true,
"parent_object_types": [
@@ -130,18 +114,6 @@
}
},
"user_handlers": {
- "application_launch_statuses": {
- "enabled": true,
- "ignored_statuses": [
- "In Progress",
- "Omitted",
- "On hold",
- "Approved"
- ],
- "status_change": {
- "In Progress": []
- }
- },
"create_update_attributes": {
"role_list": [
"Pypeclub",
@@ -223,6 +195,31 @@
]
}
},
+ "application_handlers": {
+ "change_task_status": {
+ "enabled": true,
+ "status_change_on_file_open": {
+ "In Progress": [
+ "__any__"
+ ],
+ "__ignore__": [
+ "In Progress",
+ "Omitted",
+ "On Hold"
+ ]
+ },
+ "status_change_on_file_save": {
+ "In Progress": [
+ "__any__"
+ ],
+ "__ignore__": [
+ "In Progress",
+ "Omitted",
+ "On Hold"
+ ]
+ }
+ }
+ },
"publish": {
"CollectFtrackFamily": {
"enabled": true,
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
index d6efb118b9f..6ef8b560575 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
@@ -145,28 +145,6 @@
}
]
},
- {
- "type": "dict",
- "key": "status_update",
- "label": "Update status on task action",
- "is_group": true,
- "checkbox_key": "enabled",
- "children": [
- {
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },
- {
- "key": "mapping",
- "type": "dict-modifiable",
- "object_type": {
- "type": "list",
- "object_type": "text"
- }
- }
- ]
- },
{
"type": "dict",
"key": "status_task_to_parent",
@@ -447,41 +425,6 @@
"key": "user_handlers",
"label": "User Actions/Events",
"children": [
- {
- "type": "dict",
- "key": "application_launch_statuses",
- "is_group": true,
- "label": "Application - Status change on launch",
- "checkbox_key": "enabled",
- "children": [
- {
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },
- {
- "type": "label",
- "label": "Do not change status if current status is:"
- },
- {
- "type": "list",
- "key": "ignored_statuses",
- "object_type": "text"
- },
- {
- "type": "label",
- "label": "Change task's status to left side if current task status is in list on right side."
- },
- {
- "type": "dict-modifiable",
- "key": "status_change",
- "object_type": {
- "type": "list",
- "object_type": "text"
- }
- }
- ]
- },
{
"type": "dict",
"key": "create_update_attributes",
@@ -708,6 +651,53 @@
}
]
},
+ {
+ "type": "dict",
+ "key": "application_handlers",
+ "label": "Application Events",
+ "children": [
+ {
+ "type": "dict",
+ "key": "change_task_status",
+ "is_group": true,
+ "label": "Status change",
+ "checkbox_key": "enabled",
+ "children": [
+ {
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "type": "label",
+ "label": "On Open - (Change task's status to left side if current task status is in the list on right side)"
+ },
+ {
+ "type": "dict-modifiable",
+ "key": "status_change_on_file_open",
+ "label": "Status change on file open",
+ "object_type": {
+ "type": "list",
+ "object_type": "ftrack-task-statuses"
+ }
+ },
+ {
+ "type": "label",
+ "label": "On Save - (Change task's status to left side if current task status is in the list on right side)"
+ },
+ {
+ "type": "dict-modifiable",
+ "key": "status_change_on_file_save",
+ "label": "Status change on file save",
+ "object_type": {
+ "type": "list",
+ "object_type": "ftrack-task-statuses"
+ }
+ }
+ ]
+ }
+ ]
+ },
{
"type": "dict",
"collapsible": true,