mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' into feature/PYPE-481-nuke-load-last-versions-of-subs
# Conflicts: # pype/lib.py # pype/nuke/lib.py
This commit is contained in:
commit
5a8b4a7188
35 changed files with 997 additions and 802 deletions
|
|
@ -7,3 +7,6 @@ __all__ = [
|
|||
'ClockifySettings',
|
||||
'ClockifyModule'
|
||||
]
|
||||
|
||||
def tray_init(tray_widget, main_widget):
|
||||
return ClockifyModule(main_widget, tray_widget)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:]))
|
||||
|
|
|
|||
|
|
@ -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', '')
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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', '')
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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', '')
|
||||
)
|
||||
|
||||
|
|
|
|||
240
pype/ftrack/actions/action_prepare_project.py
Normal file
240
pype/ftrack/actions/action_prepare_project.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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', '')
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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', '')
|
||||
)
|
||||
|
||||
|
|
@ -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', '')
|
||||
)
|
||||
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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!')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
10
pype/lib.py
10
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue