diff --git a/pype/clockify/__init__.py b/pype/clockify/__init__.py
index 5f61acd751..063f88db73 100644
--- a/pype/clockify/__init__.py
+++ b/pype/clockify/__init__.py
@@ -7,3 +7,6 @@ __all__ = [
'ClockifySettings',
'ClockifyModule'
]
+
+def tray_init(tray_widget, main_widget):
+ return ClockifyModule(main_widget, tray_widget)
diff --git a/pype/clockify/clockify.py b/pype/clockify/clockify.py
index 0b84bf3953..1476c3d488 100644
--- a/pype/clockify/clockify.py
+++ b/pype/clockify/clockify.py
@@ -1,3 +1,4 @@
+import os
import threading
from pypeapp import style
from Qt import QtWidgets
@@ -20,9 +21,10 @@ class ClockifyModule:
self.bool_workspace_set = False
self.bool_timer_run = False
- def start_up(self):
self.clockapi.set_master(self)
self.bool_api_key_set = self.clockapi.set_api()
+
+ def tray_start(self):
if self.bool_api_key_set is False:
self.show_settings()
return
@@ -41,7 +43,7 @@ class ClockifyModule:
os.path.dirname(__file__),
'ftrack_actions'
])
- current = os.environ('FTRACK_ACTIONS_PATH', '')
+ current = os.environ.get('FTRACK_ACTIONS_PATH', '')
if current:
current += os.pathsep
os.environ['FTRACK_ACTIONS_PATH'] = current + actions_path
@@ -57,6 +59,24 @@ class ClockifyModule:
current += os.pathsep
os.environ['AVALON_ACTIONS'] = current + actions_path
+ if 'TimersManager' in modules:
+ self.timer_manager = modules['TimersManager']
+ self.timer_manager.add_module(self)
+
+ def start_timer_manager(self, data):
+ self.start_timer(data)
+
+ def stop_timer_manager(self):
+ self.stop_timer()
+
+ def timer_started(self, data):
+ if hasattr(self, 'timer_manager'):
+ self.timer_manager.start_timers(data)
+
+ def timer_stopped(self):
+ if hasattr(self, 'timer_manager'):
+ self.timer_manager.stop_timers()
+
def start_timer_check(self):
self.bool_thread_check_running = True
if self.thread_timer_check is None:
@@ -75,21 +95,81 @@ class ClockifyModule:
def check_running(self):
import time
while self.bool_thread_check_running is True:
+ bool_timer_run = False
if self.clockapi.get_in_progress() is not None:
- self.bool_timer_run = True
- else:
- self.bool_timer_run = False
- self.set_menu_visibility()
+ bool_timer_run = True
+
+ if self.bool_timer_run != bool_timer_run:
+ if self.bool_timer_run is True:
+ self.timer_stopped()
+ else:
+ actual_timer = self.clockapi.get_in_progress()
+ if not actual_timer:
+ continue
+
+ actual_project_id = actual_timer["projectId"]
+ project = self.clockapi.get_project_by_id(
+ actual_project_id
+ )
+ project_name = project["name"]
+
+ actual_timer_hierarchy = actual_timer["description"]
+ hierarchy_items = actual_timer_hierarchy.split("/")
+ task_name = hierarchy_items[-1]
+ hierarchy = hierarchy_items[:-1]
+
+ data = {
+ "task_name": task_name,
+ "hierarchy": hierarchy,
+ "project_name": project_name
+ }
+
+ self.timer_started(data)
+
+ self.bool_timer_run = bool_timer_run
+ self.set_menu_visibility()
time.sleep(5)
def stop_timer(self):
self.clockapi.finish_time_entry()
+ if self.bool_timer_run:
+ self.timer_stopped()
self.bool_timer_run = False
+ def start_timer(self, input_data):
+ actual_timer = self.clockapi.get_in_progress()
+ actual_timer_hierarchy = None
+ actual_project_id = None
+ if actual_timer is not None:
+ actual_timer_hierarchy = actual_timer.get("description")
+ actual_project_id = actual_timer.get("projectId")
+
+ desc_items = [val for val in input_data.get("hierarchy", [])]
+ desc_items.append(input_data["task_name"])
+ description = "/".join(desc_items)
+
+ project_id = self.clockapi.get_project_id(input_data["project_name"])
+
+ if (
+ actual_timer is not None and
+ description == actual_timer_hierarchy and
+ project_id == actual_project_id
+ ):
+ return
+
+ tag_ids = []
+ task_tag_id = self.clockapi.get_tag_id(input_data["task_name"])
+ if task_tag_id is not None:
+ tag_ids.append(task_tag_id)
+
+ self.clockapi.start_time_entry(
+ description, project_id, tag_ids=tag_ids
+ )
+
# Definition of Tray menu
- def tray_menu(self, parent):
+ def tray_menu(self, parent_menu):
# Menu for Tray App
- self.menu = QtWidgets.QMenu('Clockify', parent)
+ self.menu = QtWidgets.QMenu('Clockify', parent_menu)
self.menu.setProperty('submenu', 'on')
self.menu.setStyleSheet(style.load_stylesheet())
@@ -109,7 +189,7 @@ class ClockifyModule:
self.set_menu_visibility()
- return self.menu
+ parent_menu.addMenu(self.menu)
def show_settings(self):
self.widget_settings.input_api_key.setText(self.clockapi.get_api_key())
diff --git a/pype/clockify/clockify_api.py b/pype/clockify/clockify_api.py
index f5ebac0cef..ed932eedce 100644
--- a/pype/clockify/clockify_api.py
+++ b/pype/clockify/clockify_api.py
@@ -1,4 +1,5 @@
import os
+import re
import requests
import json
import datetime
@@ -22,6 +23,7 @@ class ClockifyAPI(metaclass=Singleton):
app_dir = os.path.normpath(appdirs.user_data_dir('pype-app', 'pype'))
file_name = 'clockify.json'
fpath = os.path.join(app_dir, file_name)
+ admin_permission_names = ['WORKSPACE_OWN', 'WORKSPACE_ADMIN']
master_parent = None
workspace_id = None
@@ -55,31 +57,41 @@ class ClockifyAPI(metaclass=Singleton):
return False
return True
- def validate_workspace_perm(self):
- test_project = '__test__'
- action_url = 'workspaces/{}/projects/'.format(self.workspace_id)
- body = {
- "name": test_project, "clientId": "", "isPublic": "false",
- "estimate": {"type": "AUTO"},
- "color": "#f44336", "billable": "true"
- }
- response = requests.post(
- self.endpoint + action_url,
- headers=self.headers, json=body
+ def validate_workspace_perm(self, workspace_id=None):
+ user_id = self.get_user_id()
+ if user_id is None:
+ return False
+ if workspace_id is None:
+ workspace_id = self.workspace_id
+ action_url = "/workspaces/{}/users/{}/permissions".format(
+ workspace_id, user_id
)
- if response.status_code == 201:
- self.delete_project(self.get_project_id(test_project))
- return True
- else:
- projects = self.get_projects()
- if test_project in projects:
- try:
- self.delete_project(self.get_project_id(test_project))
- return True
- except json.decoder.JSONDecodeError:
- return False
+ response = requests.get(
+ self.endpoint + action_url,
+ headers=self.headers
+ )
+ user_permissions = response.json()
+ for perm in user_permissions:
+ if perm['name'] in self.admin_permission_names:
+ return True
return False
+ def get_user_id(self):
+ action_url = 'v1/user/'
+ response = requests.get(
+ self.endpoint + action_url,
+ headers=self.headers
+ )
+ # this regex is neccessary: UNICODE strings are crashing
+ # during json serialization
+ id_regex ='\"{1}id\"{1}\:{1}\"{1}\w+\"{1}'
+ result = re.findall(id_regex, str(response.content))
+ if len(result) != 1:
+ # replace with log and better message?
+ print('User ID was not found (this is a BUG!!!)')
+ return None
+ return json.loads('{'+result[0]+'}')['id']
+
def set_workspace(self, name=None):
if name is None:
name = os.environ.get('CLOCKIFY_WORKSPACE', None)
@@ -147,6 +159,19 @@ class ClockifyAPI(metaclass=Singleton):
project["name"]: project["id"] for project in response.json()
}
+ def get_project_by_id(self, project_id, workspace_id=None):
+ if workspace_id is None:
+ workspace_id = self.workspace_id
+ action_url = 'workspaces/{}/projects/{}/'.format(
+ workspace_id, project_id
+ )
+ response = requests.get(
+ self.endpoint + action_url,
+ headers=self.headers
+ )
+
+ return response.json()
+
def get_tags(self, workspace_id=None):
if workspace_id is None:
workspace_id = self.workspace_id
@@ -279,6 +304,9 @@ class ClockifyAPI(metaclass=Singleton):
if workspace_id is None:
workspace_id = self.workspace_id
current = self.get_in_progress(workspace_id)
+ if current is None:
+ return
+
current_id = current["id"]
action_url = 'workspaces/{}/timeEntries/{}'.format(
workspace_id, current_id
diff --git a/pype/clockify/ftrack_actions/action_clockify_start.py b/pype/clockify/ftrack_actions/action_clockify_start.py
index e09d0b76e6..5b54476297 100644
--- a/pype/clockify/ftrack_actions/action_clockify_start.py
+++ b/pype/clockify/ftrack_actions/action_clockify_start.py
@@ -14,7 +14,7 @@ class StartClockify(BaseAction):
#: Action identifier.
identifier = 'clockify.start.timer'
#: Action label.
- label = 'Start timer'
+ label = 'Clockify - Start timer'
#: Action description.
description = 'Starts timer on clockify'
#: roles that are allowed to register this action
@@ -67,42 +67,3 @@ def register(session, **kw):
return
StartClockify(session).register()
-
-
-def main(arguments=None):
- '''Set up logging and register action.'''
- if arguments is None:
- arguments = []
-
- parser = argparse.ArgumentParser()
- # Allow setting of logging level from arguments.
- loggingLevels = {}
- for level in (
- logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
- logging.ERROR, logging.CRITICAL
- ):
- loggingLevels[logging.getLevelName(level).lower()] = level
-
- parser.add_argument(
- '-v', '--verbosity',
- help='Set the logging output verbosity.',
- choices=loggingLevels.keys(),
- default='info'
- )
- namespace = parser.parse_args(arguments)
-
- # Set up basic logging
- logging.basicConfig(level=loggingLevels[namespace.verbosity])
-
- session = ftrack_api.Session()
- register(session)
-
- # Wait for events
- logging.info(
- 'Registered actions and listening for events. Use Ctrl-C to abort.'
- )
- session.event_hub.wait()
-
-
-if __name__ == '__main__':
- raise SystemExit(main(sys.argv[1:]))
diff --git a/pype/ftrack/actions/action_attributes_remapper.py b/pype/ftrack/actions/action_attributes_remapper.py
index a098225125..db33fd1365 100644
--- a/pype/ftrack/actions/action_attributes_remapper.py
+++ b/pype/ftrack/actions/action_attributes_remapper.py
@@ -11,13 +11,14 @@ class AttributesRemapper(BaseAction):
#: Action identifier.
identifier = 'attributes.remapper'
#: Action label.
- label = 'Attributes Remapper'
+ label = "Pype Doctor"
+ variant = '- Attributes Remapper'
#: Action description.
description = 'Remaps attributes in avalon DB'
#: roles that are allowed to register this action
role_list = ["Pypeclub", "Administrator"]
- icon = '{}/ftrack/action_icons/AttributesRemapper.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeDoctor.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py
index 6a68eb783e..47a6bb5d5f 100644
--- a/pype/ftrack/actions/action_create_cust_attrs.py
+++ b/pype/ftrack/actions/action_create_cust_attrs.py
@@ -110,12 +110,13 @@ class CustomAttributes(BaseAction):
#: Action identifier.
identifier = 'create.update.attributes'
#: Action label.
- label = 'Create/Update Avalon Attributes'
+ label = "Pype Admin"
+ variant = '- Create/Update Avalon Attributes'
#: Action description.
description = 'Creates Avalon/Mongo ID for double check'
#: roles that are allowed to register this action
role_list = ['Pypeclub', 'Administrator']
- icon = '{}/ftrack/action_icons/CustomAttributes.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
diff --git a/pype/ftrack/actions/action_create_folders.py b/pype/ftrack/actions/action_create_folders.py
index dc969ada02..b9e10f7c30 100644
--- a/pype/ftrack/actions/action_create_folders.py
+++ b/pype/ftrack/actions/action_create_folders.py
@@ -30,11 +30,13 @@ class CreateFolders(BaseAction):
def discover(self, session, entities, event):
''' Validation '''
- not_allowed = ['assetversion']
if len(entities) != 1:
return False
+
+ not_allowed = ['assetversion', 'project']
if entities[0].entity_type.lower() in not_allowed:
return False
+
return True
def interface(self, session, entities, event):
diff --git a/pype/ftrack/actions/action_create_project_folders.py b/pype/ftrack/actions/action_create_project_structure.py
similarity index 97%
rename from pype/ftrack/actions/action_create_project_folders.py
rename to pype/ftrack/actions/action_create_project_structure.py
index ef48df7d67..74d458b5f8 100644
--- a/pype/ftrack/actions/action_create_project_folders.py
+++ b/pype/ftrack/actions/action_create_project_structure.py
@@ -13,9 +13,9 @@ class CreateProjectFolders(BaseAction):
'''Edit meta data action.'''
#: Action identifier.
- identifier = 'create.project.folders'
+ identifier = 'create.project.structure'
#: Action label.
- label = 'Create Project Folders'
+ label = 'Create Project Structure'
#: Action description.
description = 'Creates folder structure'
#: roles that are allowed to register this action
@@ -31,6 +31,11 @@ class CreateProjectFolders(BaseAction):
def discover(self, session, entities, event):
''' Validation '''
+ if len(entities) != 1:
+ return False
+
+ if entities[0].entity_type.lower() != "project":
+ return False
return True
diff --git a/pype/ftrack/actions/action_cust_attr_doctor.py b/pype/ftrack/actions/action_cust_attr_doctor.py
index a6ccb237a6..1b8f250e5b 100644
--- a/pype/ftrack/actions/action_cust_attr_doctor.py
+++ b/pype/ftrack/actions/action_cust_attr_doctor.py
@@ -12,14 +12,15 @@ class CustomAttributeDoctor(BaseAction):
#: Action identifier.
identifier = 'custom.attributes.doctor'
#: Action label.
- label = 'Custom Attributes Doctor'
+ label = "Pype Doctor"
+ variant = '- Custom Attributes Doctor'
#: Action description.
description = (
'Fix hierarchical custom attributes mainly handles, fstart'
' and fend'
)
- icon = '{}/ftrack/action_icons/TestAction.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeDoctor.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
hierarchical_ca = ['handle_start', 'handle_end', 'fstart', 'fend']
diff --git a/pype/ftrack/actions/action_delete_asset_byname.py b/pype/ftrack/actions/action_delete_asset_byname.py
index 1c05d7d88c..4f2a0e515c 100644
--- a/pype/ftrack/actions/action_delete_asset_byname.py
+++ b/pype/ftrack/actions/action_delete_asset_byname.py
@@ -13,12 +13,13 @@ class AssetsRemover(BaseAction):
#: Action identifier.
identifier = 'remove.assets'
#: Action label.
- label = 'Delete Assets by Name'
+ label = "Pype Admin"
+ variant = '- Delete Assets by Name'
#: Action description.
description = 'Removes assets from Ftrack and Avalon db with all childs'
#: roles that are allowed to register this action
role_list = ['Pypeclub', 'Administrator']
- icon = '{}/ftrack/action_icons/AssetsRemover.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
#: Db
diff --git a/pype/ftrack/actions/action_job_killer.py b/pype/ftrack/actions/action_job_killer.py
index 8ac81c4ac2..8584b26aa4 100644
--- a/pype/ftrack/actions/action_job_killer.py
+++ b/pype/ftrack/actions/action_job_killer.py
@@ -14,12 +14,13 @@ class JobKiller(BaseAction):
#: Action identifier.
identifier = 'job.killer'
#: Action label.
- label = 'Job Killer'
+ label = "Pype Admin"
+ variant = '- Job Killer'
#: Action description.
description = 'Killing selected running jobs'
#: roles that are allowed to register this action
role_list = ['Pypeclub', 'Administrator']
- icon = '{}/ftrack/action_icons/JobKiller.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
diff --git a/pype/ftrack/actions/action_prepare_project.py b/pype/ftrack/actions/action_prepare_project.py
new file mode 100644
index 0000000000..60a7435907
--- /dev/null
+++ b/pype/ftrack/actions/action_prepare_project.py
@@ -0,0 +1,240 @@
+import os
+import json
+
+from pype.vendor import ftrack_api
+from pype.ftrack import BaseAction
+from pypeapp import config
+from pype.ftrack.lib import get_avalon_attr
+
+
+class PrepareProject(BaseAction):
+ '''Edit meta data action.'''
+
+ #: Action identifier.
+ identifier = 'prepare.project'
+ #: Action label.
+ label = 'Prepare Project'
+ #: Action description.
+ description = 'Set basic attributes on the project'
+ #: roles that are allowed to register this action
+ role_list = ["Pypeclub", "Administrator", "Project manager"]
+ icon = '{}/ftrack/action_icons/PrepareProject.svg'.format(
+ os.environ.get('PYPE_STATICS_SERVER', '')
+ )
+
+ def discover(self, session, entities, event):
+ ''' Validation '''
+ if len(entities) != 1:
+ return False
+
+ if entities[0].entity_type.lower() != "project":
+ return False
+
+ return True
+
+ def interface(self, session, entities, event):
+ if event['data'].get('values', {}):
+ return
+
+ # Inform user that this may take a while
+ self.show_message(event, "Preparing data... Please wait", True)
+
+ self.log.debug("Loading custom attributes")
+ cust_attrs, hier_cust_attrs = get_avalon_attr(session, True)
+ project_defaults = config.get_presets().get("ftrack", {}).get(
+ "project_defaults", {}
+ )
+
+ self.log.debug("Preparing data which will be shown")
+ attributes_to_set = {}
+ for attr in hier_cust_attrs:
+ key = attr["key"]
+ attributes_to_set[key] = {
+ "label": attr["label"],
+ "object": attr,
+ "default": project_defaults.get(key)
+ }
+
+ for attr in cust_attrs:
+ if attr["entity_type"].lower() != "show":
+ continue
+ key = attr["key"]
+ attributes_to_set[key] = {
+ "label": attr["label"],
+ "object": attr,
+ "default": project_defaults.get(key)
+ }
+
+ # Sort by label
+ attributes_to_set = dict(sorted(
+ attributes_to_set.items(),
+ key=lambda x: x[1]["label"]
+ ))
+ self.log.debug("Preparing interface for keys: \"{}\"".format(
+ str([key for key in attributes_to_set])
+ ))
+
+ title = "Set Attribute values"
+ items = []
+ multiselect_enumerators = []
+
+ # This item will be last (before enumerators)
+ # - sets value of auto synchronization
+ auto_sync_name = "avalon_auto_sync"
+ auto_sync_item = {
+ "name": auto_sync_name,
+ "type": "boolean",
+ "value": project_defaults.get(auto_sync_name, False),
+ "label": "AutoSync to Avalon"
+ }
+
+ item_splitter = {'type': 'label', 'value': '---'}
+
+ for key, in_data in attributes_to_set.items():
+ attr = in_data["object"]
+
+ # initial item definition
+ item = {
+ "name": key,
+ "label": in_data["label"]
+ }
+
+ # cust attr type - may have different visualization
+ type_name = attr["type"]["name"].lower()
+ easy_types = ["text", "boolean", "date", "number"]
+
+ easy_type = False
+ if type_name in easy_types:
+ easy_type = True
+
+ elif type_name == "enumerator":
+
+ attr_config = json.loads(attr["config"])
+ attr_config_data = json.loads(attr_config["data"])
+
+ if attr_config["multiSelect"] is True:
+ multiselect_enumerators.append(item_splitter)
+
+ multiselect_enumerators.append({
+ "type": "label",
+ "value": in_data["label"]
+ })
+
+ default = in_data["default"]
+ names = []
+ for option in sorted(
+ attr_config_data, key=lambda x: x["menu"]
+ ):
+ name = option["value"]
+ new_name = "__{}__{}".format(key, name)
+ names.append(new_name)
+ item = {
+ "name": new_name,
+ "type": "boolean",
+ "label": "- {}".format(option["menu"])
+ }
+ if default:
+ if (
+ isinstance(default, list) or
+ isinstance(default, tuple)
+ ):
+ if name in default:
+ item["value"] = True
+ else:
+ if name == default:
+ item["value"] = True
+
+ multiselect_enumerators.append(item)
+
+ multiselect_enumerators.append({
+ "type": "hidden",
+ "name": "__hidden__{}".format(key),
+ "value": json.dumps(names)
+ })
+ else:
+ easy_type = True
+ item["data"] = attr_config_data
+
+ else:
+ self.log.warning((
+ "Custom attribute \"{}\" has type \"{}\"."
+ " I don't know how to handle"
+ ).format(key, type_name))
+ items.append({
+ "type": "label",
+ "value": (
+ "!!! Can't handle Custom attritubte type \"{}\""
+ " (key: \"{}\")"
+ ).format(type_name, key)
+ })
+
+ if easy_type:
+ item["type"] = type_name
+
+ # default value in interface
+ default = in_data["default"]
+ if default is not None:
+ item["value"] = default
+
+ items.append(item)
+
+ # Add autosync attribute
+ items.append(auto_sync_item)
+
+ # Add enumerator items at the end
+ for item in multiselect_enumerators:
+ items.append(item)
+
+ return {
+ 'items': items,
+ 'title': title
+ }
+
+ def launch(self, session, entities, event):
+ if not event['data'].get('values', {}):
+ return
+
+ in_data = event['data']['values']
+ # Find hidden items for multiselect enumerators
+ keys_to_process = []
+ for key in in_data:
+ if key.startswith("__hidden__"):
+ keys_to_process.append(key)
+
+ self.log.debug("Preparing data for Multiselect Enumerators")
+ enumerators = {}
+ for key in keys_to_process:
+ new_key = key.replace("__hidden__", "")
+ enumerator_items = in_data.pop(key)
+ enumerators[new_key] = json.loads(enumerator_items)
+
+ # find values set for multiselect enumerator
+ for key, enumerator_items in enumerators.items():
+ in_data[key] = []
+
+ name = "__{}__".format(key)
+
+ for item in enumerator_items:
+ value = in_data.pop(item)
+ if value is True:
+ new_key = item.replace(name, "")
+ in_data[key].append(new_key)
+
+ self.log.debug("Setting Custom Attribute values:")
+ entity = entities[0]
+ for key, value in in_data.items():
+ entity["custom_attributes"][key] = value
+ self.log.debug("- Key \"{}\" set to \"{}\"".format(key, value))
+
+ session.commit()
+
+ return True
+
+
+def register(session, plugins_presets={}):
+ '''Register plugin. Called when used as an plugin.'''
+
+ if not isinstance(session, ftrack_api.session.Session):
+ return
+
+ PrepareProject(session, plugins_presets).register()
diff --git a/pype/ftrack/actions/action_sync_hier_attrs_local.py b/pype/ftrack/actions/action_sync_hier_attrs_local.py
index 31d95f9a6f..01434470f3 100644
--- a/pype/ftrack/actions/action_sync_hier_attrs_local.py
+++ b/pype/ftrack/actions/action_sync_hier_attrs_local.py
@@ -19,11 +19,12 @@ class SyncHierarchicalAttrs(BaseAction):
#: Action identifier.
identifier = 'sync.hierarchical.attrs.local'
#: Action label.
- label = 'Sync HierAttrs - Local'
+ label = "Pype Admin"
+ variant = '- Sync Hier Attrs (Local)'
#: Action description.
description = 'Synchronize hierarchical attributes'
#: Icon
- icon = '{}/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
diff --git a/pype/ftrack/actions/action_sync_to_avalon_local.py b/pype/ftrack/actions/action_sync_to_avalon_local.py
index 6e603b33b3..ad39b0ca12 100644
--- a/pype/ftrack/actions/action_sync_to_avalon_local.py
+++ b/pype/ftrack/actions/action_sync_to_avalon_local.py
@@ -47,11 +47,12 @@ class SyncToAvalon(BaseAction):
#: Action identifier.
identifier = 'sync.to.avalon.local'
#: Action label.
- label = 'SyncToAvalon - Local'
+ label = "Pype Admin"
+ variant = '- Sync To Avalon (Local)'
#: Action description.
description = 'Send data from Ftrack to Avalon'
#: Action icon.
- icon = '{}/ftrack/action_icons/SyncToAvalon-local.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
#: roles that are allowed to register this action
@@ -59,7 +60,7 @@ class SyncToAvalon(BaseAction):
#: Action priority
priority = 200
- def __init__(self, session):
+ def __init__(self, session, plugins_presets):
super(SyncToAvalon, self).__init__(session)
# reload utils on initialize (in case of server restart)
diff --git a/pype/ftrack/actions/action_thumbToChildern.py b/pype/ftrack/actions/action_thumbnail_to_childern.py
similarity index 95%
rename from pype/ftrack/actions/action_thumbToChildern.py
rename to pype/ftrack/actions/action_thumbnail_to_childern.py
index 99ca713662..101b678512 100644
--- a/pype/ftrack/actions/action_thumbToChildern.py
+++ b/pype/ftrack/actions/action_thumbnail_to_childern.py
@@ -14,9 +14,11 @@ class ThumbToChildren(BaseAction):
# Action identifier
identifier = 'thumb.to.children'
# Action label
- label = 'Thumbnail to Children'
+ label = 'Thumbnail'
+ # Action variant
+ variant = " to Children"
# Action icon
- icon = '{}/ftrack/action_icons/thumbToChildren.svg'.format(
+ icon = '{}/ftrack/action_icons/Thumbnail.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
diff --git a/pype/ftrack/actions/action_thumbToParent.py b/pype/ftrack/actions/action_thumbnail_to_parent.py
similarity index 96%
rename from pype/ftrack/actions/action_thumbToParent.py
rename to pype/ftrack/actions/action_thumbnail_to_parent.py
index 9de9cd8cc4..c382d9303c 100644
--- a/pype/ftrack/actions/action_thumbToParent.py
+++ b/pype/ftrack/actions/action_thumbnail_to_parent.py
@@ -13,9 +13,11 @@ class ThumbToParent(BaseAction):
# Action identifier
identifier = 'thumb.to.parent'
# Action label
- label = 'Thumbnail to Parent'
+ label = 'Thumbnail'
+ # Action variant
+ variant = " to Parent"
# Action icon
- icon = '{}/ftrack/action_icons/thumbToParent.svg'.format(
+ icon = '{}/ftrack/action_icons/Thumbnail.svg'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
diff --git a/pype/ftrack/events/action_sync_hier_attrs.py b/pype/ftrack/events/action_sync_hier_attrs.py
index cd7446f5e4..22ad7bf5aa 100644
--- a/pype/ftrack/events/action_sync_hier_attrs.py
+++ b/pype/ftrack/events/action_sync_hier_attrs.py
@@ -20,11 +20,12 @@ class SyncHierarchicalAttrs(BaseAction):
#: Action identifier.
identifier = 'sync.hierarchical.attrs'
#: Action label.
- label = 'Sync HierAttrs'
+ label = "Pype Admin"
+ variant = '- Sync Hier Attrs (server)'
#: Action description.
description = 'Synchronize hierarchical attributes'
#: Icon
- icon = '{}/ftrack/action_icons/SyncHierarchicalAttrs.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get(
'PYPE_STATICS_SERVER',
'http://localhost:{}'.format(
diff --git a/pype/ftrack/events/action_sync_to_avalon.py b/pype/ftrack/events/action_sync_to_avalon.py
index fc03180bdc..5628554c85 100644
--- a/pype/ftrack/events/action_sync_to_avalon.py
+++ b/pype/ftrack/events/action_sync_to_avalon.py
@@ -48,11 +48,12 @@ class Sync_To_Avalon(BaseAction):
#: Action identifier.
identifier = 'sync.to.avalon'
#: Action label.
- label = 'SyncToAvalon'
+ label = "Pype Admin"
+ variant = "- Sync To Avalon (Server)"
#: Action description.
description = 'Send data from Ftrack to Avalon'
#: Action icon.
- icon = '{}/ftrack/action_icons/SyncToAvalon.svg'.format(
+ icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format(
os.environ.get(
'PYPE_STATICS_SERVER',
'http://localhost:{}'.format(
diff --git a/pype/ftrack/ftrack_server/ftrack_server.py b/pype/ftrack/ftrack_server/ftrack_server.py
index 69637ac3ee..2a58c12d09 100644
--- a/pype/ftrack/ftrack_server/ftrack_server.py
+++ b/pype/ftrack/ftrack_server/ftrack_server.py
@@ -29,8 +29,8 @@ PYTHONPATH # Path to ftrack_api and paths to all modules used in actions
"""
-class FtrackServer():
- def __init__(self, type='action'):
+class FtrackServer:
+ def __init__(self, server_type='action'):
"""
- 'type' is by default set to 'action' - Runs Action server
- enter 'event' for Event server
@@ -45,21 +45,12 @@ class FtrackServer():
ftrack_log = logging.getLogger("ftrack_api")
ftrack_log.setLevel(logging.WARNING)
- self.type = type
- self.actionsAvailable = True
- self.eventsAvailable = True
- # Separate all paths
- if "FTRACK_ACTIONS_PATH" in os.environ:
- all_action_paths = os.environ["FTRACK_ACTIONS_PATH"]
- self.actionsPaths = all_action_paths.split(os.pathsep)
- else:
- self.actionsAvailable = False
+ env_key = "FTRACK_ACTIONS_PATH"
+ if server_type.lower() == 'event':
+ env_key = "FTRACK_EVENTS_PATH"
- if "FTRACK_EVENTS_PATH" in os.environ:
- all_event_paths = os.environ["FTRACK_EVENTS_PATH"]
- self.eventsPaths = all_event_paths.split(os.pathsep)
- else:
- self.eventsAvailable = False
+ self.server_type = server_type
+ self.env_key = env_key
def stop_session(self):
if self.session.event_hub.connected is True:
@@ -94,9 +85,7 @@ class FtrackServer():
# separate files by register function
if 'register' not in mod_functions:
- msg = (
- '"{0}" - Missing register method'
- ).format(file, self.type)
+ msg = ('"{}" - Missing register method').format(file)
log.warning(msg)
continue
@@ -115,7 +104,7 @@ class FtrackServer():
# Load presets for setting plugins
key = "user"
- if self.type.lower() == "event":
+ if self.server_type.lower() == "event":
key = "server"
plugins_presets = config.get_presets().get(
"ftrack", {}
@@ -142,24 +131,15 @@ class FtrackServer():
def run_server(self):
self.session = ftrack_api.Session(auto_connect_event_hub=True,)
- if self.type.lower() == 'event':
- if self.eventsAvailable is False:
- msg = (
- 'FTRACK_EVENTS_PATH is not set'
- ', event server won\'t launch'
- )
- log.error(msg)
- return
- self.set_files(self.eventsPaths)
- else:
- if self.actionsAvailable is False:
- msg = (
- 'FTRACK_ACTIONS_PATH is not set'
- ', action server won\'t launch'
- )
- log.error(msg)
- return
- self.set_files(self.actionsPaths)
+ paths_str = os.environ.get(self.env_key)
+ if paths_str is None:
+ log.error((
+ "Env var \"{}\" is not set, \"{}\" server won\'t launch"
+ ).format(self.env_key, self.server_type))
+ return
+
+ paths = paths_str.split(os.pathsep)
+ self.set_files(paths)
log.info(60*"*")
log.info('Registration of actions/events has finished!')
diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py
index 962dc1165c..169bc4b051 100644
--- a/pype/ftrack/lib/avalon_sync.py
+++ b/pype/ftrack/lib/avalon_sync.py
@@ -326,13 +326,26 @@ def import_to_avalon(
return output
-def get_avalon_attr(session):
+def get_avalon_attr(session, split_hierarchical=False):
custom_attributes = []
+ hier_custom_attributes = []
query = 'CustomAttributeGroup where name is "avalon"'
all_avalon_attr = session.query(query).one()
for cust_attr in all_avalon_attr['custom_attribute_configurations']:
- if 'avalon_' not in cust_attr['key']:
- custom_attributes.append(cust_attr)
+ if 'avalon_' in cust_attr['key']:
+ continue
+
+ if split_hierarchical:
+ if cust_attr["is_hierarchical"]:
+ hier_custom_attributes.append(cust_attr)
+ continue
+
+ custom_attributes.append(cust_attr)
+
+ if split_hierarchical:
+ # return tuple
+ return custom_attributes, hier_custom_attributes
+
return custom_attributes
diff --git a/pype/lib.py b/pype/lib.py
index 18adfa355d..c926601a14 100644
--- a/pype/lib.py
+++ b/pype/lib.py
@@ -467,10 +467,18 @@ def filter_pyblish_plugins(plugins):
host = api.current_host()
+ presets = config.get_presets().get('plugins', {}).get(host, {}).get(
+ "publish", {}
+ )
+
# iterate over plugins
for plugin in plugins[:]:
+ # skip if there are no presets to process
+ if not presets:
+ continue
+
try:
- config_data = config.get_presets()['plugins'][host]["publish"][plugin.__name__] # noqa: E501
+ config_data = presets[plugin.__name__] # noqa: E501
except KeyError:
continue
diff --git a/pype/plugins/nukestudio/publish/collect_tag_framestart.py b/pype/plugins/nukestudio/publish/collect_tag_framestart.py
index c2778ea680..c73a2dd1ee 100644
--- a/pype/plugins/nukestudio/publish/collect_tag_framestart.py
+++ b/pype/plugins/nukestudio/publish/collect_tag_framestart.py
@@ -1,5 +1,5 @@
from pyblish import api
-
+import os
class CollectClipTagFrameStart(api.InstancePlugin):
"""Collect FrameStart from Tags of selected track items."""
@@ -19,8 +19,20 @@ class CollectClipTagFrameStart(api.InstancePlugin):
# gets only task family tags and collect labels
if "frameStart" in t_family:
+ t_value = t_metadata.get("tag.value", "")
+
+ # backward compatibility
t_number = t_metadata.get("tag.number", "")
- start_frame = int(t_number)
+
+ try:
+ start_frame = int(t_number) or int(t_value)
+ except ValueError:
+ if "source" in t_value:
+ source_first = instance.data["sourceFirst"]
+ source_in = instance.data["sourceIn"]
+ handle_start = instance.data["handleStart"]
+ start_frame = (source_first + source_in) - handle_start
+
instance.data["startingFrame"] = start_frame
self.log.info("Start frame on `{0}` set to `{1}`".format(
instance, start_frame
diff --git a/res/ftrack/action_icons/AttributesRemapper.svg b/res/ftrack/action_icons/AttributesRemapper.svg
deleted file mode 100644
index 94bf8c4f14..0000000000
--- a/res/ftrack/action_icons/AttributesRemapper.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/res/ftrack/action_icons/CustomAttributes.svg b/res/ftrack/action_icons/CustomAttributes.svg
deleted file mode 100644
index ee1af3378e..0000000000
--- a/res/ftrack/action_icons/CustomAttributes.svg
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
diff --git a/res/ftrack/action_icons/JobKiller.svg b/res/ftrack/action_icons/JobKiller.svg
deleted file mode 100644
index 595c780a9b..0000000000
--- a/res/ftrack/action_icons/JobKiller.svg
+++ /dev/null
@@ -1,374 +0,0 @@
-
-
diff --git a/res/ftrack/action_icons/PrepareProject.svg b/res/ftrack/action_icons/PrepareProject.svg
new file mode 100644
index 0000000000..bd6b460ce3
--- /dev/null
+++ b/res/ftrack/action_icons/PrepareProject.svg
@@ -0,0 +1,88 @@
+
+
diff --git a/res/ftrack/action_icons/PypeAdmin.svg b/res/ftrack/action_icons/PypeAdmin.svg
new file mode 100644
index 0000000000..c95a29dacb
--- /dev/null
+++ b/res/ftrack/action_icons/PypeAdmin.svg
@@ -0,0 +1,173 @@
+
+
+
+
diff --git a/res/ftrack/action_icons/PypeDoctor.svg b/res/ftrack/action_icons/PypeDoctor.svg
new file mode 100644
index 0000000000..e921d99ee5
--- /dev/null
+++ b/res/ftrack/action_icons/PypeDoctor.svg
@@ -0,0 +1,114 @@
+
+
diff --git a/res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg b/res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg
deleted file mode 100644
index f58448ac06..0000000000
--- a/res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/res/ftrack/action_icons/SyncToAvalon-local.svg b/res/ftrack/action_icons/SyncToAvalon-local.svg
deleted file mode 100644
index bf4708e8a5..0000000000
--- a/res/ftrack/action_icons/SyncToAvalon-local.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/res/ftrack/action_icons/Thumbnail.svg b/res/ftrack/action_icons/Thumbnail.svg
new file mode 100644
index 0000000000..a8780b9a04
--- /dev/null
+++ b/res/ftrack/action_icons/Thumbnail.svg
@@ -0,0 +1,136 @@
+
+
diff --git a/res/ftrack/action_icons/thumbToChildren.svg b/res/ftrack/action_icons/thumbToChildren.svg
deleted file mode 100644
index 709b9549f3..0000000000
--- a/res/ftrack/action_icons/thumbToChildren.svg
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
diff --git a/res/ftrack/action_icons/thumbToParent.svg b/res/ftrack/action_icons/thumbToParent.svg
deleted file mode 100644
index 3efa426332..0000000000
--- a/res/ftrack/action_icons/thumbToParent.svg
+++ /dev/null
@@ -1,130 +0,0 @@
-
-
-
-
diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.png b/setup/nukestudio/hiero_plugin_path/Icons/retiming.png
new file mode 100644
index 0000000000..4487ac0422
Binary files /dev/null and b/setup/nukestudio/hiero_plugin_path/Icons/retiming.png differ
diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.psd b/setup/nukestudio/hiero_plugin_path/Icons/retiming.psd
new file mode 100644
index 0000000000..bac6fc6b58
Binary files /dev/null and b/setup/nukestudio/hiero_plugin_path/Icons/retiming.psd differ