From d53548fb475141ad2611ac05391c185a7baf2f71 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Nov 2018 16:21:30 +0100 Subject: [PATCH 01/12] First commit --- pype/ftrack/actions/action_syncToAvalon.py | 3 +- pype/ftrack/events/event_sync_to_avalon.py | 224 ++++++++++++++++++ pype/ftrack/events/ftrack_event_handler.py | 157 ++++++++++++ .../events/{ => old}/file_version_statuses.py | 0 .../events/{ => old}/new_task_update.py | 0 pype/ftrack/events/{ => old}/radio_buttons.py | 0 .../events/{ => old}/thumbnail_updates.py | 0 .../{ => old}/version_to_task_status.py | 0 pype/ftrack/events/test_event.py | 6 +- 9 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 pype/ftrack/events/event_sync_to_avalon.py create mode 100644 pype/ftrack/events/ftrack_event_handler.py rename pype/ftrack/events/{ => old}/file_version_statuses.py (100%) rename pype/ftrack/events/{ => old}/new_task_update.py (100%) rename pype/ftrack/events/{ => old}/radio_buttons.py (100%) rename pype/ftrack/events/{ => old}/thumbnail_updates.py (100%) rename pype/ftrack/events/{ => old}/version_to_task_status.py (100%) diff --git a/pype/ftrack/actions/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index a5ee5ee49b..bdf8649714 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -292,10 +292,11 @@ class SyncToAvalon(BaseAction): # TODO check if is asset in same folder!!! ???? FEATURE FOR FUTURE print("Asset {} - updated".format(name)) + entityId = io.find_one({'type': 'asset', 'name': name})['_id'] ## FTRACK FEATURE - FTRACK MUST HAVE avalon_mongo_id FOR EACH ENTITY TYPE EXCEPT TASK # Set custom attribute to avalon/mongo id of entity (parentID is last) if ca_mongoid in entity['custom_attributes']: - entity['custom_attributes'][ca_mongoid] = str(parentId) + entity['custom_attributes'][ca_mongoid] = str(entityId) else: print("Custom attribute for <{}> is not created.".format(entity['name'])) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py new file mode 100644 index 0000000000..be5aef1086 --- /dev/null +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -0,0 +1,224 @@ +import os +import sys +import ftrack_api +from ftrack_event_handler import BaseEvent +from avalon import io, inventory, lib +from avalon.vendor import toml +import re + +class Sync_to_Avalon(BaseEvent): + + def launch(self, session, entities, event): + self.ca_mongoid = 'avalon_mongo_id' + + self.proj = None + for entity in entities: + try: + base_proj = entity['link'][0] + except: + continue + self.proj = session.get(base_proj['type'], base_proj['id']) + break + + if self.proj is None: + return + + os.environ["AVALON_PROJECT"] = self.proj['full_name'] + + proj_id = self.proj['custom_attributes'][self.ca_mongoid] + + io.install() + self.avalon_project = io.find({"_id": proj_id}) + self.projectId = proj_id + if self.avalon_project is None: + self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) + self.projectId = self.avalon_project['_id'] + io.uninstall() + + self.importEntities = [] + exceptions = ['assetversion', 'job', 'user'] + + for entity in entities: + if entity.entity_type.lower() in exceptions: + continue + elif entity.entity_type.lower() in ['task']: + entity = entity['parent'] + try: + mongo_id = entity['custom_attributes'][self.ca_mongoid] + except: + return { + 'success': False, + 'message': "Please run 'Create Attributes' action or create custom attribute 'avalon_mongo_id' manually for {}".format(entity.entity_type) + } + + if entity not in self.importEntities: + self.importEntities.append(entity) + + if len(self.importEntities) < 1: + return + + self.setAvalonAttributes() + + io.install() + + for entity in self.importEntities: + self.importToAvalon(entity) + + io.uninstall() + + return True + + def importToAvalon(self, entity): + data = {} + + type = 'asset' + name = entity['name'] + print(1000*"*") + print(name) + silo = 'Film' + if entity.entity_type == 'Project': + type = 'project' + name = entity['full_name'] + data['code'] = entity['name'] + elif entity.entity_type in ['AssetBuild', 'Library']: + silo = 'Assets' + + os.environ["AVALON_ASSET"] = name + os.environ["AVALON_SILO"] = silo + + entity_type = entity.entity_type + + data['ftrackId'] = entity['id'] + data['entityType'] = entity_type + + for cust_attr in self.custom_attributes: + if cust_attr['entity_type'].lower() in ['asset']: + data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + + elif cust_attr['entity_type'].lower() in ['show'] and entity_type.lower() == 'project': + data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + + elif cust_attr['entity_type'].lower() in ['task'] and entity_type.lower() != 'project': + # Put space between capitals (e.g. 'AssetBuild' -> 'Asset Build') + entity_type = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) + # Get object id of entity type + ent_obj_type_id = self.session.query('ObjectType where name is "{}"'.format(entity_type)).one()['id'] + + if cust_attr['object_type_id'] == ent_obj_type_id: + data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + + mongo_id = entity['custom_attributes'][self.ca_mongoid] + avalon_asset = io.find({'_id': mongo_id}) + if avalon_asset is None: + avalon_asset = io.find_one({'type': type, 'name': name}) + + if entity_type in ['project']: + config = self.getConfig() + template = {"schema": "avalon-core:inventory-1.0"} + + if self.avalon_project is None: + mongo_id = inventory.save(self.proj['full_name'], config, template) + + self.avalon_project = io.find({"_id": mongo_id}) + self.projectId = mongo_id + if self.avalon_project is None: + self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) + self.projectId = self.avalon_project['_id'] + + io.update_many( + {"_id": mongo_id}, + {'$set':{ + 'name':name, + 'config':config, + 'data':data, + }}) + return + + eLinks = [] + for e in entity['link']: + tmp = self.session.get(e['type'], e['id']) + eLinks.append(tmp) + + tasks = [] + for child in entity['children']: + if child.entity_type in ['Task']: + tasks.append(child['name']) + + folderStruct = [] + parents = [] + for i in range(1, len(eLinks)-1): + parents.append(eLinks[i]) + + for parent in parents: + name = self.checkName(parent['name']) + folderStruct.append(name) + parentId = io.find_one({'type': 'asset', 'name': name})['_id'] + if parent['parent'].entity_type != 'project' and parentId is None: + self.importToAvalon(parent) + parentId = io.find_one({'type': 'asset', 'name': name})['_id'] + + hierarchy = os.path.sep.join(folderStruct) + + data['tasks'] = tasks + data['parents'] = folderStruct + data['visualParent'] = parentId + data['hierarchy'] = hierarchy + + if self.avalon_project is None: + self.importToAvalon(self.proj) + + if avalon_asset is None: + mongo_id = inventory.create_asset(name, silo, data, self.projectId) + + io.update_many( + {"_id": mongo_id}, + {'$set':{ + 'name':name, + 'silo':silo, + 'data':data, + 'Parent': self.projectId}}) + + def checkName(self, input_name): + if input_name.find(" ") == -1: + name = input_name + else: + name = input_name.replace(" ", "-") + print("Name of {} was changed to {}".format(input_name, name)) + return name + + def getConfig(self, entity): + apps = [] + for app in entity['custom_attributes']['applications']: + try: + label = toml.load(lib.which_app(app))['label'] + apps.append({'name':app, 'label':label}) + except Exception as e: + print('Error with application {0} - {1}'.format(app, e)) + + config = { + 'schema': 'avalon-core:config-1.0', + 'tasks': [{'name': ''}], + 'apps': apps, + # TODO redo work!!! + 'template': { + 'workfile': '{asset[name]}_{task[name]}_{version:0>3}<_{comment}>', + 'work': '{root}/{project}/{hierarchy}/{asset}/work/{task}', + 'publish':'{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}'} + } + return config + + def setAvalonAttributes(self): + self.custom_attributes = [] + all_avalon_attr = self.session.query('CustomAttributeGroup where name is "avalon"').one() + for cust_attr in all_avalon_attr['custom_attribute_configurations']: + if 'avalon_' not in cust_attr['key']: + self.custom_attributes.append(cust_attr) + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + if not isinstance(session, ftrack_api.session.Session): + return + + event = Sync_to_Avalon(session) + event.register() diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py new file mode 100644 index 0000000000..a8a4ffe420 --- /dev/null +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -0,0 +1,157 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack +import os +import logging +import getpass +import platform +import ftrack_api +import toml +from avalon import io, lib, pipeline +from avalon import session as sess +import acre + +from app.api import ( + Templates, + Logger +) + + +class BaseEvent(object): + '''Custom Action base class + + `label` a descriptive string identifing your action. + + `varaint` To group actions together, give them the same + label and specify a unique variant per action. + + `identifier` a unique identifier for your action. + + `description` a verbose descriptive text for you action + + ''' + + def __init__(self, session): + '''Expects a ftrack_api.Session instance''' + + self.logger = logging.getLogger( + '{0}.{1}'.format(__name__, self.__class__.__name__) + ) + + self._session = session + + @property + def session(self): + '''Return current session.''' + return self._session + + def register(self): + '''Registers the event, subscribing the the discover and launch topics.''' + self.session.event_hub.subscribe('topic=ftrack.update', self._launch) + + def _translate_event(self, session, event): + '''Return *event* translated structure to be used with the API.''' + print(100*"-") + print(event) + _selection = event['data'].get('entities',[]) + + _entities = list() + for entity in _selection: + if entity['entityType'] in ['socialfeed']: + continue + _entities.append( + ( + session.get(self._get_entity_type(entity), entity.get('entityId')) + # self._get_entity_type(entity), entity.get('entityId') + ) + ) + + return [ + _entities, + event + ] + + def _get_entity_type(self, entity): + '''Return translated entity type tht can be used with API.''' + # Get entity type and make sure it is lower cased. Most places except + # the component tab in the Sidebar will use lower case notation. + entity_type = entity.get('entityType').replace('_', '').lower() + + for schema in self.session.schemas: + alias_for = schema.get('alias_for') + + if ( + alias_for and isinstance(alias_for, str) and + alias_for.lower() == entity_type + ): + return schema['id'] + + for schema in self.session.schemas: + if schema['id'].lower() == entity_type: + return schema['id'] + + raise ValueError( + 'Unable to translate entity type: {0}.'.format(entity_type) + ) + + def _launch(self, event): + args = self._translate_event( + self.session, event + ) + + response = self.launch( + self.session, *args + ) + + return self._handle_result( + self.session, response, *args + ) + + def launch(self, session, entities, event): + '''Callback method for the custom action. + + return either a bool ( True if successful or False if the action failed ) + or a dictionary with they keys `message` and `success`, the message should be a + string and will be displayed as feedback to the user, success should be a bool, + True if successful or False if the action failed. + + *session* is a `ftrack_api.Session` instance + + *entities* is a list of tuples each containing the entity type and the entity id. + If the entity is a hierarchical you will always get the entity + type TypedContext, once retrieved through a get operation you + will have the "real" entity type ie. example Shot, Sequence + or Asset Build. + + *event* the unmodified original event + + ''' + raise NotImplementedError() + + + def _handle_result(self, session, result, entities, event): + '''Validate the returned result from the action callback''' + if isinstance(result, bool): + result = { + 'success': result, + 'message': ( + '{0} launched successfully.'.format( + self.__class__.__name__ + ) + ) + } + + elif isinstance(result, dict): + for key in ('success', 'message'): + if key in result: + continue + + raise KeyError( + 'Missing required key: {0}.'.format(key) + ) + + else: + self.logger.error( + 'Invalid result type must be bool or dictionary!' + ) + + return result diff --git a/pype/ftrack/events/file_version_statuses.py b/pype/ftrack/events/old/file_version_statuses.py similarity index 100% rename from pype/ftrack/events/file_version_statuses.py rename to pype/ftrack/events/old/file_version_statuses.py diff --git a/pype/ftrack/events/new_task_update.py b/pype/ftrack/events/old/new_task_update.py similarity index 100% rename from pype/ftrack/events/new_task_update.py rename to pype/ftrack/events/old/new_task_update.py diff --git a/pype/ftrack/events/radio_buttons.py b/pype/ftrack/events/old/radio_buttons.py similarity index 100% rename from pype/ftrack/events/radio_buttons.py rename to pype/ftrack/events/old/radio_buttons.py diff --git a/pype/ftrack/events/thumbnail_updates.py b/pype/ftrack/events/old/thumbnail_updates.py similarity index 100% rename from pype/ftrack/events/thumbnail_updates.py rename to pype/ftrack/events/old/thumbnail_updates.py diff --git a/pype/ftrack/events/version_to_task_status.py b/pype/ftrack/events/old/version_to_task_status.py similarity index 100% rename from pype/ftrack/events/version_to_task_status.py rename to pype/ftrack/events/old/version_to_task_status.py diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index 3be53492d4..7e5fe6d903 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -1,6 +1,5 @@ # import ftrack_api as local session import ftrack_api -from utils import print_entity_head # session = ftrack_api.Session() @@ -14,8 +13,7 @@ def test_event(event): for entity in event['data'].get('entities', []): if entity['entityType'] == 'task' and entity['action'] == 'update': - print "\n\nevent script: {}".format(__file__) - print_entity_head.print_entity_head(entity, session) + print("\n\nevent script: {}".format(__file__)) # for k in task.keys(): # print k, task[k] @@ -23,6 +21,6 @@ def test_event(event): # print task['assignments'] for e in entity.keys(): - print '{0}: {1}'.format(e, entity[e]) + print('{0}: {1}'.format(e, entity[e])) # end of event procedure ---------------------------------- From b8d803c2e70468887a880644dbfe9e188d16528f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Nov 2018 17:08:16 +0100 Subject: [PATCH 02/12] Event seems to work, need more testing and refactoring with action --- pype/ftrack/events/event_sync_to_avalon.py | 27 +++++++++++----------- pype/ftrack/events/ftrack_event_handler.py | 2 -- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index be5aef1086..d8ede03503 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -5,6 +5,7 @@ from ftrack_event_handler import BaseEvent from avalon import io, inventory, lib from avalon.vendor import toml import re +from bson.objectid import ObjectId class Sync_to_Avalon(BaseEvent): @@ -28,7 +29,7 @@ class Sync_to_Avalon(BaseEvent): proj_id = self.proj['custom_attributes'][self.ca_mongoid] io.install() - self.avalon_project = io.find({"_id": proj_id}) + self.avalon_project = io.find({"_id": ObjectId(proj_id)}) self.projectId = proj_id if self.avalon_project is None: self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) @@ -73,8 +74,6 @@ class Sync_to_Avalon(BaseEvent): type = 'asset' name = entity['name'] - print(1000*"*") - print(name) silo = 'Film' if entity.entity_type == 'Project': type = 'project' @@ -108,9 +107,7 @@ class Sync_to_Avalon(BaseEvent): data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] mongo_id = entity['custom_attributes'][self.ca_mongoid] - avalon_asset = io.find({'_id': mongo_id}) - if avalon_asset is None: - avalon_asset = io.find_one({'type': type, 'name': name}) + if entity_type in ['project']: config = self.getConfig() @@ -119,14 +116,14 @@ class Sync_to_Avalon(BaseEvent): if self.avalon_project is None: mongo_id = inventory.save(self.proj['full_name'], config, template) - self.avalon_project = io.find({"_id": mongo_id}) + self.avalon_project = io.find({"_id": ObjectId(mongo_id)}) self.projectId = mongo_id if self.avalon_project is None: self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) self.projectId = self.avalon_project['_id'] io.update_many( - {"_id": mongo_id}, + {"_id": ObjectId(mongo_id)}, {'$set':{ 'name':name, 'config':config, @@ -150,12 +147,12 @@ class Sync_to_Avalon(BaseEvent): parents.append(eLinks[i]) for parent in parents: - name = self.checkName(parent['name']) - folderStruct.append(name) - parentId = io.find_one({'type': 'asset', 'name': name})['_id'] + parname = self.checkName(parent['name']) + folderStruct.append(parname) + parentId = io.find_one({'type': 'asset', 'name': parname})['_id'] if parent['parent'].entity_type != 'project' and parentId is None: self.importToAvalon(parent) - parentId = io.find_one({'type': 'asset', 'name': name})['_id'] + parentId = io.find_one({'type': 'asset', 'name': parname})['_id'] hierarchy = os.path.sep.join(folderStruct) @@ -167,17 +164,21 @@ class Sync_to_Avalon(BaseEvent): if self.avalon_project is None: self.importToAvalon(self.proj) + avalon_asset = io.find_one({'_id': ObjectId(mongo_id)}) + if avalon_asset is None: + avalon_asset = io.find_one({'type': type, 'name': name}) if avalon_asset is None: mongo_id = inventory.create_asset(name, silo, data, self.projectId) io.update_many( - {"_id": mongo_id}, + {"_id": ObjectId(mongo_id)}, {'$set':{ 'name':name, 'silo':silo, 'data':data, 'Parent': self.projectId}}) + def checkName(self, input_name): if input_name.find(" ") == -1: name = input_name diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index a8a4ffe420..775d6c07c2 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -50,8 +50,6 @@ class BaseEvent(object): def _translate_event(self, session, event): '''Return *event* translated structure to be used with the API.''' - print(100*"-") - print(event) _selection = event['data'].get('entities',[]) _entities = list() From 8c093ce2adc5220b266cab8b33d5f5e235f980bb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 26 Nov 2018 14:26:44 +0100 Subject: [PATCH 03/12] Creating backup --- pype/ftrack/actions/action_syncToAvalon.py | 57 +++++-------- pype/ftrack/events/event_sync_to_avalon.py | 89 ++++++++++---------- pype/ftrack/events/ftrack_event_handler.py | 1 - pype/ftrack/events/test_event.py | 36 ++++---- pype/ftrack/ftrack_utils.py | 29 ++++++- pype/lib.py | 26 ++++++ pype/utils/__init__.py | 98 ---------------------- pype/utils/lib.py | 96 +++++++++++++++++++++ 8 files changed, 229 insertions(+), 203 deletions(-) diff --git a/pype/ftrack/actions/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index bdf8649714..0530dde69c 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -7,10 +7,11 @@ import os import ftrack_api import json import re +from pype import lib from ftrack_action_handler import BaseAction - -from avalon import io, inventory, lib +from avalon import io, inventory from avalon.vendor import toml +from pype.ftrack import ftrack_utils class SyncToAvalon(BaseAction): '''Edit meta data action.''' @@ -27,6 +28,7 @@ class SyncToAvalon(BaseAction): def discover(self, session, entities, event): ''' Validation ''' + discover = False for entity in entities: if entity.entity_type.lower() not in ['task', 'assetversion']: @@ -55,7 +57,7 @@ class SyncToAvalon(BaseAction): print("action <" + self.__class__.__name__ + "> is running") #TODO AVALON_PROJECTS, AVALON_ASSET, AVALON_SILO should be set up otherwise console log shows avalon debug - self.setAvalonAttributes(session) + self.setAvalonAttributes() self.importable = [] # get from top entity in hierarchy all parent entities @@ -107,9 +109,10 @@ class SyncToAvalon(BaseAction): 'success': True, 'message': "Synchronization was successfull" } - def setAvalonAttributes(self, session): + + def setAvalonAttributes(self): self.custom_attributes = [] - all_avalon_attr = session.query('CustomAttributeGroup where name is "avalon"').one() + all_avalon_attr = self.session.query('CustomAttributeGroup where name is "avalon"').one() for cust_attr in all_avalon_attr['custom_attribute_configurations']: if 'avalon_' not in cust_attr['key']: self.custom_attributes.append(cust_attr) @@ -132,28 +135,6 @@ class SyncToAvalon(BaseAction): print("Name of {} was changed to {}".format(input_name, name)) return name - def getConfig(self, entity): - apps = [] - for app in entity['custom_attributes']['applications']: - try: - label = toml.load(lib.which_app(app))['label'] - apps.append({'name':app, 'label':label}) - except Exception as e: - print('Error with application {0} - {1}'.format(app, e)) - - config = { - 'schema': 'avalon-core:config-1.0', - 'tasks': [{'name': ''}], - 'apps': apps, - # TODO redo work!!! - 'template': { - 'workfile': '{asset[name]}_{task[name]}_{version:0>3}<_{comment}>', - 'work': '{root}/{project}/{hierarchy}/{asset}/work/{task}', - 'publish':'{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}'} - } - return config - - def importToAvalon(self, session, entity): eLinks = [] @@ -170,9 +151,6 @@ class SyncToAvalon(BaseAction): os.environ["AVALON_PROJECT"] = entityProj["full_name"] os.environ["AVALON_ASSET"] = entityProj['full_name'] - # Set project template - template = {"schema": "avalon-core:inventory-1.0"} - # --- Begin: PUSH TO Avalon --- io.install() ## ----- PROJECT ------ @@ -185,25 +163,28 @@ class SyncToAvalon(BaseAction): data['entityType'] = entity_type for cust_attr in self.custom_attributes: + key = cust_attr['key'] if cust_attr['entity_type'].lower() in ['asset']: - data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + data[key] = entity['custom_attributes'][key] elif cust_attr['entity_type'].lower() in ['show'] and entity_type.lower() == 'project': - data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + data[key] = entity['custom_attributes'][key] elif cust_attr['entity_type'].lower() in ['task'] and entity_type.lower() != 'project': # Put space between capitals (e.g. 'AssetBuild' -> 'Asset Build') - entity_type = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) + entity_type_full = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) # Get object id of entity type - ent_obj_type_id = session.query('ObjectType where name is "{}"'.format(entity_type)).one()['id'] + ent_obj_type_id = session.query('ObjectType where name is "{}"'.format(entity_type_full)).one()['id'] if cust_attr['object_type_id'] == ent_obj_type_id: - data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + data[key] = entity['custom_attributes'][key] - if entity.entity_type.lower() in ['project']: + if entity_type.lower() in ['project']: # Set project Config - config = self.getConfig(entity) + config = ftrack_utils.get_config(entity) + # Set project template + template = lib.get_avalon_project_template_schema() if avalon_project is None: inventory.save(entityProj['full_name'], config, template) @@ -233,7 +214,7 @@ class SyncToAvalon(BaseAction): ## ----- ASSETS ------ # Presets: # TODO how to check if entity is Asset Library or AssetBuild? - if entity.entity_type in ['AssetBuild', 'Library']: + if entity_type in ['AssetBuild', 'Library']: silo = 'Assets' else: silo = 'Film' diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index d8ede03503..4c09251b53 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -1,18 +1,20 @@ import os import sys +import re import ftrack_api from ftrack_event_handler import BaseEvent -from avalon import io, inventory, lib +from pype import lib +from avalon import io, inventory from avalon.vendor import toml -import re from bson.objectid import ObjectId +from pype.ftrack import ftrack_utils class Sync_to_Avalon(BaseEvent): def launch(self, session, entities, event): self.ca_mongoid = 'avalon_mongo_id' - self.proj = None + for entity in entities: try: base_proj = entity['link'][0] @@ -37,12 +39,9 @@ class Sync_to_Avalon(BaseEvent): io.uninstall() self.importEntities = [] - exceptions = ['assetversion', 'job', 'user'] for entity in entities: - if entity.entity_type.lower() in exceptions: - continue - elif entity.entity_type.lower() in ['task']: + if entity.entity_type.lower() in ['task']: entity = entity['parent'] try: mongo_id = entity['custom_attributes'][self.ca_mongoid] @@ -72,46 +71,48 @@ class Sync_to_Avalon(BaseEvent): def importToAvalon(self, entity): data = {} + entity_type = entity.entity_type + type = 'asset' name = entity['name'] silo = 'Film' - if entity.entity_type == 'Project': + if entity_type in ['Project']: type = 'project' name = entity['full_name'] data['code'] = entity['name'] - elif entity.entity_type in ['AssetBuild', 'Library']: + elif entity_type in ['AssetBuild', 'Library']: silo = 'Assets' os.environ["AVALON_ASSET"] = name os.environ["AVALON_SILO"] = silo - entity_type = entity.entity_type - data['ftrackId'] = entity['id'] data['entityType'] = entity_type for cust_attr in self.custom_attributes: + key = cust_attr['key'] if cust_attr['entity_type'].lower() in ['asset']: - data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + data[key] = entity['custom_attributes'][key] elif cust_attr['entity_type'].lower() in ['show'] and entity_type.lower() == 'project': - data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + data[key] = entity['custom_attributes'][key] elif cust_attr['entity_type'].lower() in ['task'] and entity_type.lower() != 'project': # Put space between capitals (e.g. 'AssetBuild' -> 'Asset Build') - entity_type = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) + entity_type_full = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) # Get object id of entity type - ent_obj_type_id = self.session.query('ObjectType where name is "{}"'.format(entity_type)).one()['id'] + ent_obj_type_id = self.session.query('ObjectType where name is "{}"'.format(entity_type_full)).one()['id'] if cust_attr['object_type_id'] == ent_obj_type_id: - data[cust_attr['key']] = entity['custom_attributes'][cust_attr['key']] + data[key] = entity['custom_attributes'][key] mongo_id = entity['custom_attributes'][self.ca_mongoid] - if entity_type in ['project']: - config = self.getConfig() - template = {"schema": "avalon-core:inventory-1.0"} + if entity_type.lower() in ['project']: + + config = ftrack_utils.get_config(entity) + template = lib.get_avalon_project_template_schema() if self.avalon_project is None: mongo_id = inventory.save(self.proj['full_name'], config, template) @@ -131,6 +132,10 @@ class Sync_to_Avalon(BaseEvent): }}) return + + if self.avalon_project is None: + self.importToAvalon(self.proj) + eLinks = [] for e in entity['link']: tmp = self.session.get(e['type'], e['id']) @@ -161,13 +166,12 @@ class Sync_to_Avalon(BaseEvent): data['visualParent'] = parentId data['hierarchy'] = hierarchy - if self.avalon_project is None: - self.importToAvalon(self.proj) - avalon_asset = io.find_one({'_id': ObjectId(mongo_id)}) if avalon_asset is None: avalon_asset = io.find_one({'type': type, 'name': name}) - if avalon_asset is None: + if avalon_asset is None: + mongo_id = inventory.create_asset(name, silo, data, self.projectId) + elif avalon_asset['name'] != name: mongo_id = inventory.create_asset(name, silo, data, self.projectId) io.update_many( @@ -176,7 +180,7 @@ class Sync_to_Avalon(BaseEvent): 'name':name, 'silo':silo, 'data':data, - 'Parent': self.projectId}}) + 'parent': self.projectId}}) def checkName(self, input_name): @@ -187,27 +191,6 @@ class Sync_to_Avalon(BaseEvent): print("Name of {} was changed to {}".format(input_name, name)) return name - def getConfig(self, entity): - apps = [] - for app in entity['custom_attributes']['applications']: - try: - label = toml.load(lib.which_app(app))['label'] - apps.append({'name':app, 'label':label}) - except Exception as e: - print('Error with application {0} - {1}'.format(app, e)) - - config = { - 'schema': 'avalon-core:config-1.0', - 'tasks': [{'name': ''}], - 'apps': apps, - # TODO redo work!!! - 'template': { - 'workfile': '{asset[name]}_{task[name]}_{version:0>3}<_{comment}>', - 'work': '{root}/{project}/{hierarchy}/{asset}/work/{task}', - 'publish':'{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}'} - } - return config - def setAvalonAttributes(self): self.custom_attributes = [] all_avalon_attr = self.session.query('CustomAttributeGroup where name is "avalon"').one() @@ -215,6 +198,22 @@ class Sync_to_Avalon(BaseEvent): if 'avalon_' not in cust_attr['key']: self.custom_attributes.append(cust_attr) + def _translate_event(self, session, event): + exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog'] + _selection = event['data'].get('entities',[]) + + _entities = list() + for entity in _selection: + if entity['entityType'] in exceptions: + continue + _entities.append( + ( + session.get(self._get_entity_type(entity), entity.get('entityId')) + ) + ) + + return [_entities, event] + def register(session, **kw): '''Register plugin. Called when used as an plugin.''' diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index 775d6c07c2..a555a5324a 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -59,7 +59,6 @@ class BaseEvent(object): _entities.append( ( session.get(self._get_entity_type(entity), entity.get('entityId')) - # self._get_entity_type(entity), entity.get('entityId') ) ) diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index 7e5fe6d903..bf15928f98 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -1,26 +1,26 @@ -# import ftrack_api as local session +import os +import sys import ftrack_api -# -session = ftrack_api.Session() - -# ---------------------------------- +from ftrack_event_handler import BaseEvent -def test_event(event): - '''just a testing event''' +class Test_Event(BaseEvent): - # start of event procedure ---------------------------------- - for entity in event['data'].get('entities', []): - if entity['entityType'] == 'task' and entity['action'] == 'update': + def launch(self, session, entities, event): + '''just a testing event''' + exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog'] + selection = event['data'].get('entities',[]) + for entity in selection: + if entity['entityType'] in exceptions: + print(100*"*") + print(entity) - print("\n\nevent script: {}".format(__file__)) - # for k in task.keys(): - # print k, task[k] - # print '\n' - # print task['assignments'] +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' - for e in entity.keys(): - print('{0}: {1}'.format(e, entity[e])) + if not isinstance(session, ftrack_api.session.Session): + return - # end of event procedure ---------------------------------- + event = Test_Event(session) + event.register() diff --git a/pype/ftrack/ftrack_utils.py b/pype/ftrack/ftrack_utils.py index 23531a9fdd..68e83a9e6e 100644 --- a/pype/ftrack/ftrack_utils.py +++ b/pype/ftrack/ftrack_utils.py @@ -2,13 +2,36 @@ import ftrack_api import os +import traceback from pprint import * +from pype import lib +def get_apps(entity): + """ Get apps from project + Requirements: + 'Entity' MUST be object of ftrack entity with entity_type 'Project' + Checking if app from ftrack is available in Templates/bin/{app_name}.toml -def checkLogin(): - # check Environments FTRACK_API_USER, FTRACK_API_KEY - pass + Returns: + Array with dictionaries with app Name and Label + """ + apps = [] + for app in entity['custom_attributes']['applications']: + try: + label = toml.load(lib.which_app(app))['label'] + apps.append({'name':app, 'label':label}) + except Exception as e: + print('Error with application {0} - {1}'.format(app, e)) + return apps +def get_config(self, entity): + config = {} + config['schema'] = lib.get_avalon_project_config_schema() + config['tasks'] = [{'name': ''}] + config['apps'] = get_apps(entity) + config['template'] = lib.get_avalon_project_template() + + return config def checkRegex(): # _handle_result -> would be solution? diff --git a/pype/lib.py b/pype/lib.py index 3ce1441e3d..34bdda2a17 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -335,3 +335,29 @@ def get_asset_data(asset=None): data = document.get("data", {}) return data + +def get_avalon_project_config_schema(): + schema = 'avalon-core:config-1.0' + return schema + +def get_avalon_project_template_schema(): + schema = {"schema": "avalon-core:inventory-1.0"} + return schema + +def get_avalon_project_template(): + from app.api import Templates + + """Get avalon template + + Returns: + dictionary with templates + """ + template = Templates(type=["anatomy"]) + proj_template = {} + # proj_template['workfile'] = '{asset[name]}_{task[name]}_{version:0>3}<_{comment}>' + # proj_template['work'] = '{root}/{project}/{hierarchy}/{asset}/work/{task}' + # proj_template['publish'] = '{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}' + proj_template['workfile'] = template.anatomy.avalon.workfile + proj_template['work'] = template.anatomy.avalon.work + proj_template['publish'] = template.anatomy.avalon.publish + return proj_template diff --git a/pype/utils/__init__.py b/pype/utils/__init__.py index 318d875f60..e69de29bb2 100644 --- a/pype/utils/__init__.py +++ b/pype/utils/__init__.py @@ -1,98 +0,0 @@ -from .lib import * - - -def load_capture_preset(path): - import capture_gui - import capture - - path = path - preset = capture_gui.lib.load_json(path) - print preset - - options = dict() - - # CODEC - id = 'Codec' - for key in preset[id]: - options[str(key)] = preset[id][key] - - # GENERIC - id = 'Generic' - for key in preset[id]: - if key.startswith('isolate'): - pass - # options['isolate'] = preset[id][key] - else: - options[str(key)] = preset[id][key] - - # RESOLUTION - id = 'Resolution' - options['height'] = preset[id]['height'] - options['width'] = preset[id]['width'] - - # DISPLAY OPTIONS - id = 'Display Options' - disp_options = {} - for key in preset['Display Options']: - if key.startswith('background'): - disp_options[key] = preset['Display Options'][key] - else: - disp_options['displayGradient'] = True - - options['display_options'] = disp_options - - # VIEWPORT OPTIONS - temp_options = {} - id = 'Renderer' - for key in preset[id]: - temp_options[str(key)] = preset[id][key] - - temp_options2 = {} - id = 'Viewport Options' - light_options = {0: "default", - 1: 'all', - 2: 'selected', - 3: 'flat', - 4: 'nolights'} - for key in preset[id]: - if key == 'high_quality': - temp_options2['multiSampleEnable'] = True - temp_options2['multiSampleCount'] = 4 - temp_options2['textureMaxResolution'] = 512 - temp_options2['enableTextureMaxRes'] = True - - if key == 'alphaCut': - temp_options2['transparencyAlgorithm'] = 5 - temp_options2['transparencyQuality'] = 1 - - if key == 'headsUpDisplay': - temp_options['headsUpDisplay'] = True - - if key == 'displayLights': - temp_options[str(key)] = light_options[preset[id][key]] - else: - temp_options[str(key)] = preset[id][key] - - for key in ['override_viewport_options', 'high_quality', 'alphaCut']: - temp_options.pop(key, None) - - options['viewport_options'] = temp_options - options['viewport2_options'] = temp_options2 - - # use active sound track - scene = capture.parse_active_scene() - options['sound'] = scene['sound'] - cam_options = dict() - cam_options['overscan'] = 1.0 - cam_options['displayFieldChart'] = False - cam_options['displayFilmGate'] = False - cam_options['displayFilmOrigin'] = False - cam_options['displayFilmPivot'] = False - cam_options['displayGateMask'] = False - cam_options['displayResolution'] = False - cam_options['displaySafeAction'] = False - cam_options['displaySafeTitle'] = False - - # options['display_options'] = temp_options - - return options diff --git a/pype/utils/lib.py b/pype/utils/lib.py index 8fdc1a4455..8b7be1a3fe 100644 --- a/pype/utils/lib.py +++ b/pype/utils/lib.py @@ -105,3 +105,99 @@ def filter_instances(context, plugin): instances = pyblish.api.instances_by_plugin(allInstances, plugin) return instances + +def load_capture_preset(path): + import capture_gui + import capture + + path = path + preset = capture_gui.lib.load_json(path) + print preset + + options = dict() + + # CODEC + id = 'Codec' + for key in preset[id]: + options[str(key)] = preset[id][key] + + # GENERIC + id = 'Generic' + for key in preset[id]: + if key.startswith('isolate'): + pass + # options['isolate'] = preset[id][key] + else: + options[str(key)] = preset[id][key] + + # RESOLUTION + id = 'Resolution' + options['height'] = preset[id]['height'] + options['width'] = preset[id]['width'] + + # DISPLAY OPTIONS + id = 'Display Options' + disp_options = {} + for key in preset['Display Options']: + if key.startswith('background'): + disp_options[key] = preset['Display Options'][key] + else: + disp_options['displayGradient'] = True + + options['display_options'] = disp_options + + # VIEWPORT OPTIONS + temp_options = {} + id = 'Renderer' + for key in preset[id]: + temp_options[str(key)] = preset[id][key] + + temp_options2 = {} + id = 'Viewport Options' + light_options = {0: "default", + 1: 'all', + 2: 'selected', + 3: 'flat', + 4: 'nolights'} + for key in preset[id]: + if key == 'high_quality': + temp_options2['multiSampleEnable'] = True + temp_options2['multiSampleCount'] = 4 + temp_options2['textureMaxResolution'] = 512 + temp_options2['enableTextureMaxRes'] = True + + if key == 'alphaCut': + temp_options2['transparencyAlgorithm'] = 5 + temp_options2['transparencyQuality'] = 1 + + if key == 'headsUpDisplay': + temp_options['headsUpDisplay'] = True + + if key == 'displayLights': + temp_options[str(key)] = light_options[preset[id][key]] + else: + temp_options[str(key)] = preset[id][key] + + for key in ['override_viewport_options', 'high_quality', 'alphaCut']: + temp_options.pop(key, None) + + options['viewport_options'] = temp_options + options['viewport2_options'] = temp_options2 + + # use active sound track + scene = capture.parse_active_scene() + options['sound'] = scene['sound'] + cam_options = dict() + cam_options['overscan'] = 1.0 + cam_options['displayFieldChart'] = False + cam_options['displayFilmGate'] = False + cam_options['displayFilmOrigin'] = False + cam_options['displayFilmPivot'] = False + cam_options['displayGateMask'] = False + cam_options['displayResolution'] = False + cam_options['displaySafeAction'] = False + cam_options['displaySafeTitle'] = False + + # options['display_options'] = temp_options + + return options From 01cf4230ed131f7842f8ea62c2e1a3a13359a937 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 27 Nov 2018 09:43:47 +0100 Subject: [PATCH 04/12] Creating Backup --- pype/ftrack/events/ftrack_event_handler.py | 9 ++++++--- pype/ftrack/events/test_event.py | 3 +-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index a555a5324a..10a2eff0d1 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -3,7 +3,7 @@ import os import logging import getpass -import platform +# import platform import ftrack_api import toml from avalon import io, lib, pipeline @@ -33,7 +33,7 @@ class BaseEvent(object): def __init__(self, session): '''Expects a ftrack_api.Session instance''' - self.logger = logging.getLogger( + self.logger = Logger.getLogger( '{0}.{1}'.format(__name__, self.__class__.__name__) ) @@ -61,7 +61,10 @@ class BaseEvent(object): session.get(self._get_entity_type(entity), entity.get('entityId')) ) ) - + try: + if _entities[0]['project'].entity_type in ['project']: + _entities = None + _entities = list() return [ _entities, event diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index bf15928f98..7839168970 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -2,7 +2,7 @@ import os import sys import ftrack_api from ftrack_event_handler import BaseEvent - +from app import api class Test_Event(BaseEvent): @@ -18,7 +18,6 @@ class Test_Event(BaseEvent): def register(session, **kw): '''Register plugin. Called when used as an plugin.''' - if not isinstance(session, ftrack_api.session.Session): return From 5aa0edffd499a544ea0372dbf53aa276f3fbb411 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 27 Nov 2018 19:19:17 +0100 Subject: [PATCH 05/12] Events work but don't show messag eto user --- pype/ftrack/actions/action_syncToAvalon.py | 24 ----- pype/ftrack/events/event_sync_to_avalon.py | 113 ++++++++++++++------- pype/ftrack/events/ftrack_event_handler.py | 19 ++-- pype/ftrack/events/test_event.py | 10 +- pype/ftrack/ftrack_utils.py | 2 +- 5 files changed, 96 insertions(+), 72 deletions(-) diff --git a/pype/ftrack/actions/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index 1c3cc25264..e2cf4e07f8 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -134,31 +134,7 @@ class SyncToAvalon(BaseAction): self.log.info("Name of {} was changed to {}".format(input_name, name)) return name -<<<<<<< HEAD -======= - def getConfig(self, entity): - apps = [] - for app in entity['custom_attributes']['applications']: - try: - label = toml.load(lib.which_app(app))['label'] - apps.append({'name':app, 'label':label}) - except Exception as e: - self.log.error('Error with application {0} - {1}'.format(app, e)) - config = { - 'schema': 'avalon-core:config-1.0', - 'tasks': [{'name': ''}], - 'apps': apps, - # TODO redo work!!! - 'template': { - 'workfile': '{asset[name]}_{task[name]}_{version:0>3}<_{comment}>', - 'work': '{root}/{project}/{hierarchy}/{asset}/work/{task}', - 'publish':'{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}'} - } - return config - - ->>>>>>> develop def importToAvalon(self, session, entity): eLinks = [] diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index 4c09251b53..c4c0db0d55 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -13,7 +13,12 @@ class Sync_to_Avalon(BaseEvent): def launch(self, session, entities, event): self.ca_mongoid = 'avalon_mongo_id' + for ent in event['data']['entities']: + if self.ca_mongoid in ent['keys']: + return False self.proj = None + self.nameShotAsset = [] + self.nameChanged = [] for entity in entities: try: @@ -24,22 +29,28 @@ class Sync_to_Avalon(BaseEvent): break if self.proj is None: - return + return False os.environ["AVALON_PROJECT"] = self.proj['full_name'] - proj_id = self.proj['custom_attributes'][self.ca_mongoid] + self.projectId = self.proj['custom_attributes'][self.ca_mongoid] io.install() - self.avalon_project = io.find({"_id": ObjectId(proj_id)}) - self.projectId = proj_id + try: + self.avalon_project = io.find_one({"_id": ObjectId(self.projectId)}) + except: + self.avalon_project = None + + importEntities = [] + if self.avalon_project is None: self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) - self.projectId = self.avalon_project['_id'] + if self.avalon_project is None: + importEntities.append(self.proj) + else: + self.projectId = self.avalon_project['_id'] io.uninstall() - self.importEntities = [] - for entity in entities: if entity.entity_type.lower() in ['task']: entity = entity['parent'] @@ -51,21 +62,36 @@ class Sync_to_Avalon(BaseEvent): 'message': "Please run 'Create Attributes' action or create custom attribute 'avalon_mongo_id' manually for {}".format(entity.entity_type) } - if entity not in self.importEntities: - self.importEntities.append(entity) + if entity not in importEntities: + importEntities.append(entity) - if len(self.importEntities) < 1: - return + if len(importEntities) < 1: + return False self.setAvalonAttributes() io.install() - - for entity in self.importEntities: + for entity in importEntities: self.importToAvalon(entity) io.uninstall() + message = "" + if len(self.nameChanged) > 0: + names = ", ".join(self.nameChanged) + message += "These entities name can't be changed in avalon, please reset DB or use restore action: {} \n".format(names) + if len(self.nameShotAsset) > 0: + names = ", ".join(self.nameChanged) + message += "These entities are already used in avalon, duplicates with new name were created: {}".format(names) + + session.commit() + + if message != "": + return { + 'success': False, + 'message': message + } + return True def importToAvalon(self, entity): @@ -108,28 +134,28 @@ class Sync_to_Avalon(BaseEvent): mongo_id = entity['custom_attributes'][self.ca_mongoid] - - if entity_type.lower() in ['project']: - + if entity_type in ['Project']: config = ftrack_utils.get_config(entity) template = lib.get_avalon_project_template_schema() if self.avalon_project is None: - mongo_id = inventory.save(self.proj['full_name'], config, template) + inventory.save(name, config, template) + self.avalon_project = io.find_one({'type': 'project', 'name': name}) - self.avalon_project = io.find({"_id": ObjectId(mongo_id)}) - self.projectId = mongo_id - if self.avalon_project is None: - self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) - self.projectId = self.avalon_project['_id'] + self.projectId = self.avalon_project['_id'] + data['code'] = entity['name'] io.update_many( - {"_id": ObjectId(mongo_id)}, + {"_id": ObjectId(self.projectId)}, {'$set':{ 'name':name, 'config':config, 'data':data, }}) + try: + entity['custom_attributes'][self.ca_mongoid] = str(self.projectId) + except Exception as e: + self.log.error(e) return @@ -148,31 +174,45 @@ class Sync_to_Avalon(BaseEvent): folderStruct = [] parents = [] + parentId = None + for i in range(1, len(eLinks)-1): parents.append(eLinks[i]) for parent in parents: parname = self.checkName(parent['name']) folderStruct.append(parname) - parentId = io.find_one({'type': 'asset', 'name': parname})['_id'] - if parent['parent'].entity_type != 'project' and parentId is None: + avalonAarent = io.find_one({'type': 'asset', 'name': parname}) + if parent['parent'].entity_type != 'project' and avalonAarent is None: self.importToAvalon(parent) - parentId = io.find_one({'type': 'asset', 'name': parname})['_id'] + parentId = io.find_one({'type': 'asset', 'name': parname})['_id'] hierarchy = os.path.sep.join(folderStruct) data['tasks'] = tasks - data['parents'] = folderStruct - data['visualParent'] = parentId - data['hierarchy'] = hierarchy + if parentId is not None: + data['parents'] = folderStruct + data['visualParent'] = parentId + data['hierarchy'] = hierarchy + + avalon_asset = None + + if mongo_id is not "": + avalon_asset = io.find_one({'_id': ObjectId(mongo_id)}) - avalon_asset = io.find_one({'_id': ObjectId(mongo_id)}) if avalon_asset is None: - avalon_asset = io.find_one({'type': type, 'name': name}) + avalon_asset = io.find_one({'type': 'asset', 'name': name}) if avalon_asset is None: - mongo_id = inventory.create_asset(name, silo, data, self.projectId) - elif avalon_asset['name'] != name: - mongo_id = inventory.create_asset(name, silo, data, self.projectId) + mongo_id = inventory.create_asset(name, silo, data, ObjectId(self.projectId)) + else: + if name != avalon_asset['name']: + string = "'{}->{}'".format(name, avalon_asset['name']) + if entity_type in ['Shot','AssetBuild']: + self.nameShotAsset.append(string) + mongo_id = inventory.create_asset(name, silo, data, ObjectId(self.projectId)) + else: + self.nameChanged.append(string) + return io.update_many( {"_id": ObjectId(mongo_id)}, @@ -182,6 +222,11 @@ class Sync_to_Avalon(BaseEvent): 'data':data, 'parent': self.projectId}}) + try: + entity['custom_attributes'][self.ca_mongoid] = str(mongo_id) + except Exception as e: + self.log.error(e) + def checkName(self, input_name): if input_name.find(" ") == -1: diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index 10a2eff0d1..009c2b2d57 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -33,9 +33,7 @@ class BaseEvent(object): def __init__(self, session): '''Expects a ftrack_api.Session instance''' - self.logger = Logger.getLogger( - '{0}.{1}'.format(__name__, self.__class__.__name__) - ) + self.log = Logger.getLogger(self.__class__.__name__) self._session = session @@ -61,10 +59,7 @@ class BaseEvent(object): session.get(self._get_entity_type(entity), entity.get('entityId')) ) ) - try: - if _entities[0]['project'].entity_type in ['project']: - _entities = None - _entities = list() + return [ _entities, event @@ -98,6 +93,14 @@ class BaseEvent(object): self.session, event ) + # TODO REMOVE THIS - ONLY FOR TEST PROJECT + for a in args[0]: + try: + if (a['project']['name'] != 'eventproj'): + return True + except: + continue + response = self.launch( self.session, *args ) @@ -150,7 +153,7 @@ class BaseEvent(object): ) else: - self.logger.error( + self.log.error( 'Invalid result type must be bool or dictionary!' ) diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index 7839168970..aa3bbb6e0d 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -1,5 +1,6 @@ import os import sys +import re import ftrack_api from ftrack_event_handler import BaseEvent from app import api @@ -7,13 +8,12 @@ from app import api class Test_Event(BaseEvent): def launch(self, session, entities, event): + '''just a testing event''' exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog'] - selection = event['data'].get('entities',[]) - for entity in selection: - if entity['entityType'] in exceptions: - print(100*"*") - print(entity) + + + return True def register(session, **kw): diff --git a/pype/ftrack/ftrack_utils.py b/pype/ftrack/ftrack_utils.py index 68e83a9e6e..7891aae0b9 100644 --- a/pype/ftrack/ftrack_utils.py +++ b/pype/ftrack/ftrack_utils.py @@ -24,7 +24,7 @@ def get_apps(entity): print('Error with application {0} - {1}'.format(app, e)) return apps -def get_config(self, entity): +def get_config(entity): config = {} config['schema'] = lib.get_avalon_project_config_schema() config['tasks'] = [{'name': ''}] From d6a15e259a10fdf07e3c81a2b24539f0985cbd4f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 29 Nov 2018 11:28:50 +0100 Subject: [PATCH 06/12] Creating backup --- pype/ftrack/events/event_show_message.py | 32 ++++++++++++++++++++++ pype/ftrack/events/ftrack_event_handler.py | 20 ++++++++++++++ pype/ftrack/events/test_event.py | 4 +-- 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 pype/ftrack/events/event_show_message.py diff --git a/pype/ftrack/events/event_show_message.py b/pype/ftrack/events/event_show_message.py new file mode 100644 index 0000000000..b0309713d9 --- /dev/null +++ b/pype/ftrack/events/event_show_message.py @@ -0,0 +1,32 @@ +import os +import sys +import re +import ftrack_api +from ftrack_event_handler import BaseEvent +from app import api + +class Show_Message(BaseEvent): + + def launch(self, event): + + self.session.event_hub.publish_reply(event, event['data']) + return event['data'] + + def register(self): + # self.session.event_hub.subscribe('topic=show_message_topic', self.launch) + + self.session.event_hub.subscribe( + 'topic=ftrack.action.launch and data.actionIdentifier={0} and source.user.username={1}'.format( + self.identifier, + self.session.api_user + ), + self._launch + ) + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + if not isinstance(session, ftrack_api.session.Session): + return + + event = Show_Message(session) + event.register() diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index 009c2b2d57..f3ad931985 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -46,6 +46,8 @@ class BaseEvent(object): '''Registers the event, subscribing the the discover and launch topics.''' self.session.event_hub.subscribe('topic=ftrack.update', self._launch) + self.log.info("----- event - <" + self.__class__.__name__ + "> - Has been registered -----") + def _translate_event(self, session, event): '''Return *event* translated structure to be used with the API.''' _selection = event['data'].get('entities',[]) @@ -130,6 +132,24 @@ class BaseEvent(object): ''' raise NotImplementedError() + def show_message(self, event, input_message, result = False): + if not isinstance(result, bool): + result = False + + try: + message = str(input_message) + except: + return + source = {} + source['id'] = event['source']['applicationId'] + source['user'] = event['source']['user'] + self.session.event_hub.publish_reply(event, event['data'], source) + # event = ftrack_api.event.base.Event( + # topic='show_message_topic', + # data={'success':result, 'message': message} + # ) + # + # self.session.event_hub.publish(event) def _handle_result(self, session, result, entities, event): '''Validate the returned result from the action callback''' diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index aa3bbb6e0d..19022e3360 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -11,8 +11,8 @@ class Test_Event(BaseEvent): '''just a testing event''' exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog'] - - + self.show_message(event,"Test",True) + self.log.info(event['source']) return True From 5db5d7d086c0d5fe4a233461b9786ac53ec8122c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 29 Nov 2018 15:12:20 +0100 Subject: [PATCH 07/12] Creating backup --- pype/ftrack/events/event_show_message.py | 32 ------------ pype/ftrack/events/test_event.py | 63 ++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 35 deletions(-) delete mode 100644 pype/ftrack/events/event_show_message.py diff --git a/pype/ftrack/events/event_show_message.py b/pype/ftrack/events/event_show_message.py deleted file mode 100644 index b0309713d9..0000000000 --- a/pype/ftrack/events/event_show_message.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -import sys -import re -import ftrack_api -from ftrack_event_handler import BaseEvent -from app import api - -class Show_Message(BaseEvent): - - def launch(self, event): - - self.session.event_hub.publish_reply(event, event['data']) - return event['data'] - - def register(self): - # self.session.event_hub.subscribe('topic=show_message_topic', self.launch) - - self.session.event_hub.subscribe( - 'topic=ftrack.action.launch and data.actionIdentifier={0} and source.user.username={1}'.format( - self.identifier, - self.session.api_user - ), - self._launch - ) - -def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - if not isinstance(session, ftrack_api.session.Session): - return - - event = Show_Message(session) - event.register() diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index 19022e3360..a4e42fc8ae 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -10,9 +10,37 @@ class Test_Event(BaseEvent): def launch(self, session, entities, event): '''just a testing event''' - exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog'] - self.show_message(event,"Test",True) - self.log.info(event['source']) + result = True + message = "test message" + data = { + 'success':result, + 'message': message, + } + + self.log.info(event['data']['entities']) + # event['source']['id'] + + + # self.session.event_hub.publish_reply(event, data, subscriber.metadata) + + # subscriber = None + # self.log.info("before Message") + # for s in self.session.event_hub._subscribers: + # if 'topic=custom_message_show' == str(s.subscription): + # subscriber = s + # break + # + # if subscriber is not None: + # id = subs.metadata['id'] + # + # event = ftrack_api.event.base.Event( + # topic='topic=custom_message_show', + # data=data + # ) + # self.session.event_hub.publish(event) + # self.log.info("after Message") + # self.show_message(event,"Test",True) + # self.log.info(event['source']) return True @@ -23,3 +51,32 @@ def register(session, **kw): event = Test_Event(session) event.register() + +# ] From 30c4066d2d93b9a330ed71916056d889353868cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 29 Nov 2018 16:30:07 +0100 Subject: [PATCH 08/12] events can show messages to user now --- pype/ftrack/events/event_sync_to_avalon.py | 40 +++++---------- pype/ftrack/events/ftrack_event_handler.py | 24 +++++---- pype/ftrack/events/test_event.py | 59 +--------------------- 3 files changed, 28 insertions(+), 95 deletions(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index c4c0db0d55..4854a36f8c 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -17,8 +17,6 @@ class Sync_to_Avalon(BaseEvent): if self.ca_mongoid in ent['keys']: return False self.proj = None - self.nameShotAsset = [] - self.nameChanged = [] for entity in entities: try: @@ -54,43 +52,33 @@ class Sync_to_Avalon(BaseEvent): for entity in entities: if entity.entity_type.lower() in ['task']: entity = entity['parent'] + try: mongo_id = entity['custom_attributes'][self.ca_mongoid] except: - return { - 'success': False, - 'message': "Please run 'Create Attributes' action or create custom attribute 'avalon_mongo_id' manually for {}".format(entity.entity_type) - } + message = "Please run 'Create Attributes' action or create custom attribute 'avalon_mongo_id' manually for {}".format(entity.entity_type) + self.show_message(event, message, False) + return if entity not in importEntities: importEntities.append(entity) if len(importEntities) < 1: - return False + return self.setAvalonAttributes() io.install() + for entity in importEntities: self.importToAvalon(entity) io.uninstall() - message = "" - if len(self.nameChanged) > 0: - names = ", ".join(self.nameChanged) - message += "These entities name can't be changed in avalon, please reset DB or use restore action: {} \n".format(names) - if len(self.nameShotAsset) > 0: - names = ", ".join(self.nameChanged) - message += "These entities are already used in avalon, duplicates with new name were created: {}".format(names) - session.commit() if message != "": - return { - 'success': False, - 'message': message - } + self.show_message(event, message, False) return True @@ -162,22 +150,20 @@ class Sync_to_Avalon(BaseEvent): if self.avalon_project is None: self.importToAvalon(self.proj) - eLinks = [] - for e in entity['link']: - tmp = self.session.get(e['type'], e['id']) - eLinks.append(tmp) - tasks = [] for child in entity['children']: if child.entity_type in ['Task']: tasks.append(child['name']) folderStruct = [] - parents = [] parentId = None - for i in range(1, len(eLinks)-1): - parents.append(eLinks[i]) + parents = [] + for i in range(1, len(entity['link'])-1): + tmp_type = entity['link'][i]['type'] + tmp_id = entity['link'][i]['id'] + tmp = self.session.get(tmp_type, tmp_id) + parents.append(tmp) for parent in parents: parname = self.checkName(parent['name']) diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index f3ad931985..d0ecd53192 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -140,16 +140,20 @@ class BaseEvent(object): message = str(input_message) except: return - source = {} - source['id'] = event['source']['applicationId'] - source['user'] = event['source']['user'] - self.session.event_hub.publish_reply(event, event['data'], source) - # event = ftrack_api.event.base.Event( - # topic='show_message_topic', - # data={'success':result, 'message': message} - # ) - # - # self.session.event_hub.publish(event) + + user_id = event['source']['user']['id'] + self.session.event_hub.publish( + ftrack_api.event.base.Event( + topic='ftrack.action.trigger-user-interface', + data=dict( + type='message', + success=False, + message=message + ), + target='applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) + ), + on_error='ignore' + ) def _handle_result(self, session, result, entities, event): '''Validate the returned result from the action callback''' diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/test_event.py index a4e42fc8ae..c2586aa666 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/test_event.py @@ -10,37 +10,9 @@ class Test_Event(BaseEvent): def launch(self, session, entities, event): '''just a testing event''' - result = True - message = "test message" - data = { - 'success':result, - 'message': message, - } - self.log.info(event['data']['entities']) - # event['source']['id'] + # self.show_message(event,"test",True) - - # self.session.event_hub.publish_reply(event, data, subscriber.metadata) - - # subscriber = None - # self.log.info("before Message") - # for s in self.session.event_hub._subscribers: - # if 'topic=custom_message_show' == str(s.subscription): - # subscriber = s - # break - # - # if subscriber is not None: - # id = subs.metadata['id'] - # - # event = ftrack_api.event.base.Event( - # topic='topic=custom_message_show', - # data=data - # ) - # self.session.event_hub.publish(event) - # self.log.info("after Message") - # self.show_message(event,"Test",True) - # self.log.info(event['source']) return True @@ -51,32 +23,3 @@ def register(session, **kw): event = Test_Event(session) event.register() - -# ] From d2b68baf5e89f13f4fc4b9be395867481eef7068 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sun, 2 Dec 2018 22:18:33 +0100 Subject: [PATCH 09/12] fixing setting format to use crop value --- pype/nuke/__init__.py | 27 +++++---- pype/nuke/lib.py | 131 ++++++++++++++++++++++++++---------------- pype/nuke/menu.py | 13 ++++- 3 files changed, 104 insertions(+), 67 deletions(-) diff --git a/pype/nuke/__init__.py b/pype/nuke/__init__.py index 371fe2a786..3bf972bcaf 100644 --- a/pype/nuke/__init__.py +++ b/pype/nuke/__init__.py @@ -3,7 +3,7 @@ import sys from avalon import api as avalon from pyblish import api as pyblish -from .. import api as pype +from .. import api from pype.nuke import menu @@ -15,12 +15,12 @@ import nuke # removing logger handler created in avalon_core for name, handler in [(handler.get_name(), handler) - for handler in pype.Logger.logging.root.handlers[:]]: + for handler in api.Logger.logging.root.handlers[:]]: if "pype" not in str(name).lower(): - pype.Logger.logging.root.removeHandler(handler) + api.Logger.logging.root.removeHandler(handler) -log = pype.Logger.getLogger(__name__, "nuke") +log = api.Logger.getLogger(__name__, "nuke") AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") @@ -37,7 +37,7 @@ self = sys.modules[__name__] self.nLogger = None -class NukeHandler(pype.Logger.logging.Handler): +class NukeHandler(api.Logger.logging.Handler): ''' Nuke Handler - emits logs into nuke's script editor. warning will emit nuke.warning() @@ -45,7 +45,7 @@ class NukeHandler(pype.Logger.logging.Handler): ''' def __init__(self): - pype.Logger.logging.Handler.__init__(self) + api.Logger.logging.Handler.__init__(self) self.set_name("Pype_Nuke_Handler") def emit(self, record): @@ -65,11 +65,11 @@ class NukeHandler(pype.Logger.logging.Handler): nuke_handler = NukeHandler() if nuke_handler.get_name() \ not in [handler.get_name() - for handler in pype.Logger.logging.root.handlers[:]]: - pype.Logger.logging.getLogger().addHandler(nuke_handler) + for handler in api.Logger.logging.root.handlers[:]]: + api.Logger.logging.getLogger().addHandler(nuke_handler) if not self.nLogger: - self.nLogger = pype.Logger + self.nLogger = api.Logger def reload_config(): @@ -86,8 +86,6 @@ def reload_config(): "app.api", "{}.api".format(AVALON_CONFIG), "{}.templates".format(AVALON_CONFIG), - "{}.nuke".format(AVALON_CONFIG), - "{}.nuke.lib".format(AVALON_CONFIG), "{}.nuke.templates".format(AVALON_CONFIG), "{}.nuke.menu".format(AVALON_CONFIG) ): @@ -100,7 +98,8 @@ def reload_config(): def install(): - pype.fill_avalon_workdir() + + api.fill_avalon_workdir() reload_config() log.info("Registering Nuke plug-ins..") @@ -129,7 +128,7 @@ def install(): menu.install() # load data from templates - pype.load_data_from_templates() + api.load_data_from_templates() def uninstall(): @@ -141,7 +140,7 @@ def uninstall(): pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) # reset data from templates - pype.reset_data_from_templates() + api.reset_data_from_templates() def on_pyblish_instance_toggled(instance, old_value, new_value): diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 79c292b2ba..b3ae82c609 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -2,6 +2,7 @@ import sys from collections import OrderedDict from pprint import pprint from avalon.vendor.Qt import QtGui +from avalon import api, io import avalon.nuke import pype.api as pype import nuke @@ -99,57 +100,6 @@ def add_rendering_knobs(node): return node -def update_frame_range(start, end, root=None): - """Set Nuke script start and end frame range - - Args: - start (float, int): start frame - end (float, int): end frame - root (object, Optional): root object from nuke's script - - Returns: - None - - """ - - knobs = { - "first_frame": start, - "last_frame": end - } - - with avalon.nuke.viewer_update_and_undo_stop(): - for key, value in knobs.items(): - if root: - root[key].setValue(value) - else: - nuke.root()[key].setValue(value) - - -def get_additional_data(container): - """Get Nuke's related data for the container - - Args: - container(dict): the container found by the ls() function - - Returns: - dict - """ - - node = container["_tool"] - tile_color = node['tile_color'].value() - if tile_color is None: - return {} - - hex = '%08x' % tile_color - rgba = [ - float(int(hex[0:2], 16)) / 255.0, - float(int(hex[2:4], 16)) / 255.0, - float(int(hex[4:6], 16)) / 255.0 - ] - - return {"color": QtGui.QColor().fromRgbF(rgba[0], rgba[1], rgba[2])} - - def set_viewers_colorspace(viewer): assert isinstance(viewer, dict), log.error( "set_viewers_colorspace(): argument should be dictionary") @@ -245,6 +195,85 @@ def get_avalon_knob_data(node): return None return data + +def reset_resolution(): + """Set resolution to project resolution.""" + log.info("Reseting resolution") + project = io.find_one({"type": "project"}) + asset = api.Session["AVALON_ASSET"] + asset = io.find_one({"name": asset, "type": "asset"}) + + try: + width = asset["data"].get("resolution_width", 1920) + height = asset["data"].get("resolution_height", 1080) + pixel_aspect = asset["data"].get("pixel_aspect", 1) + + bbox = asset["data"].get("crop", "0.0.1920.1080") + + try: + x, y, r, t = bbox.split(".") + except Exception as e: + x = 0 + y = 0 + r = width + t = height + log.error("{}: {} \nFormat:Crop need to be set with dots, example: " + "0.0.1920.1080, /nSetting to default".format(__name__, e)) + + except KeyError: + log.warning( + "No resolution information found for \"{0}\".".format( + project["name"] + ) + ) + return + + used_formats = list() + for f in nuke.formats(): + if project["name"] in str(f.name()): + used_formats.append(f.name()) + else: + format_name = project["name"] + "_1" + + if used_formats: + format_name = "{}_{}".format( + project["name"], + int(used_formats[-1][-1])+1 + ) + log.info("Format exists: {}. " + "Will create new: {}...".format( + used_formats[-1], + format_name) + ) + + make_format( + width=int(width), + height=int(height), + x=int(x), + y=int(y), + r=int(r), + t=int(t), + pixel_aspect=float(pixel_aspect), + project_name=format_name + ) + log.info("Format is set") + + +def make_format(**args): + log.info("Format does't exist, will create: \n{}".format(args)) + nuke.addFormat( + "{width} " + "{height} " + "{x} " + "{y} " + "{r} " + "{t} " + "{pixel_aspect} " + "{project_name}".format(**args) + ) + nuke.root()["format"].setValue("{project_name}".format(**args)) + + # TODO: bellow functions are wip and needs to be check where they are used # ------------------------------------ diff --git a/pype/nuke/menu.py b/pype/nuke/menu.py index 97e2432e16..1fb38e389d 100644 --- a/pype/nuke/menu.py +++ b/pype/nuke/menu.py @@ -5,8 +5,17 @@ from pype.nuke import lib def install(): + menubar = nuke.menu("Nuke") menu = menubar.findItem(Session["AVALON_LABEL"]) - menu.addSeparator() - menu.addCommand("Set colorspace...", lib.set_colorspace) + # replace reset resolution from avalon core to pype's + name = "Reset Resolution" + rm_item = [(i, item) + for i, item in enumerate(menu.items()) + if name in item.name()][0] + menu.removeItem(rm_item[1].name()) + menu.addCommand(rm_item[1].name(), lib.reset_resolution, index=rm_item[0]) + + # add colorspace menu item + menu.addCommand("Set colorspace...", lib.set_colorspace, index=rm_item[0]+1) From 0229c35a4d207a23100e312eab33000697100e65 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Dec 2018 13:47:13 +0100 Subject: [PATCH 10/12] wip on reset_format --- pype/nuke/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index b3ae82c609..173b3bf692 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -207,7 +207,6 @@ def reset_resolution(): width = asset["data"].get("resolution_width", 1920) height = asset["data"].get("resolution_height", 1080) pixel_aspect = asset["data"].get("pixel_aspect", 1) - bbox = asset["data"].get("crop", "0.0.1920.1080") try: @@ -236,6 +235,7 @@ def reset_resolution(): format_name = project["name"] + "_1" if used_formats: + check_format = used_formats[-1] format_name = "{}_{}".format( project["name"], int(used_formats[-1][-1])+1 @@ -246,6 +246,8 @@ def reset_resolution(): format_name) ) + # format_build + make_format( width=int(width), height=int(height), From f2796b12e04952260ff14e970bbbf8273b187e9f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Dec 2018 15:14:46 +0100 Subject: [PATCH 11/12] if format alredy exists it will not create new --- pype/nuke/lib.py | 72 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 173b3bf692..4f251bb729 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -230,49 +230,73 @@ def reset_resolution(): used_formats = list() for f in nuke.formats(): if project["name"] in str(f.name()): - used_formats.append(f.name()) + used_formats.append(f) else: format_name = project["name"] + "_1" + crnt_fmt_str = "" if used_formats: check_format = used_formats[-1] format_name = "{}_{}".format( project["name"], - int(used_formats[-1][-1])+1 + int(used_formats[-1].name()[-1])+1 ) - log.info("Format exists: {}. " - "Will create new: {}...".format( - used_formats[-1], - format_name) - ) + log.info( + "Format exists: {}. " + "Will create new: {}...".format( + used_formats[-1].name(), + format_name) + ) + crnt_fmt_kargs = { + "width": (check_format.width()), + "height": (check_format.height()), + "x": int(check_format.x()), + "y": int(check_format.y()), + "r": int(check_format.r()), + "t": int(check_format.t()), + "pixel_aspect": float(check_format.pixelAspect()) + } + crnt_fmt_str = make_format_string(**crnt_fmt_kargs) + log.info("crnt_fmt_str: {}".format(crnt_fmt_str)) - # format_build + new_fmt_kargs = { + "width": int(width), + "height": int(height), + "x": int(x), + "y": int(y), + "r": int(r), + "t": int(t), + "pixel_aspect": float(pixel_aspect), + "project_name": format_name + } - make_format( - width=int(width), - height=int(height), - x=int(x), - y=int(y), - r=int(r), - t=int(t), - pixel_aspect=float(pixel_aspect), - project_name=format_name - ) - log.info("Format is set") + new_fmt_str = make_format_string(**new_fmt_kargs) + log.info("new_fmt_str: {}".format(new_fmt_str)) + + if new_fmt_str not in crnt_fmt_str: + make_format(frm_str=new_fmt_str, + project_name=new_fmt_kargs["project_name"]) + + log.info("Format is set") -def make_format(**args): - log.info("Format does't exist, will create: \n{}".format(args)) - nuke.addFormat( +def make_format_string(**args): + format_str = ( "{width} " "{height} " "{x} " "{y} " "{r} " "{t} " - "{pixel_aspect} " - "{project_name}".format(**args) + "{pixel_aspect:.2f}".format(**args) ) + return format_str + + +def make_format(**args): + log.info("Format does't exist, will create: \n{}".format(args)) + nuke.addFormat("{frm_str} " + "{project_name}".format(**args)) nuke.root()["format"].setValue("{project_name}".format(**args)) From d1c1c4f039c363c572769b7ae1a4ba9e4a9c98bc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 5 Dec 2018 16:15:11 +0100 Subject: [PATCH 12/12] Sync to avalon event should work like action. Synchronize only projects that have checked Avalon auto-sync. --- pype/ftrack/actions/action_syncToAvalon.py | 13 +- pype/ftrack/events/event_sync_to_avalon.py | 182 ++++++++---------- .../events/{test_event.py => event_test.py} | 2 +- pype/ftrack/events/ftrack_event_handler.py | 72 ++----- pype/ftrack/ftrack_utils.py | 2 +- 5 files changed, 104 insertions(+), 167 deletions(-) rename pype/ftrack/events/{test_event.py => event_test.py} (90%) diff --git a/pype/ftrack/actions/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index c354f2332d..cad43684c9 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -219,7 +219,7 @@ class SyncToAvalon(BaseAction): }) elif self.avalon_project['name'] != entity['full_name']: - raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(avalon_asset['name'], name)) + raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(self.avalon_project['name'], name)) data = ftrack_utils.get_data(self, entity, session,self.custom_attributes) @@ -235,7 +235,7 @@ class SyncToAvalon(BaseAction): if self.ca_mongoid in entity['custom_attributes']: entity['custom_attributes'][self.ca_mongoid] = str(self.projectId) else: - self.log.error("Custom attribute for <{}> is not created.".format(entity['name'])) + self.log.error('Custom attribute for "{}" is not created.'.format(entity['name'])) return ## ----- ASSETS ------ @@ -271,15 +271,12 @@ class SyncToAvalon(BaseAction): self.log.debug("Asset {} - created".format(name)) # Raise error if it seems to be different ent. with same name - else: - aD = avalon_asset['data'] - # check_attr = ['parents', 'ftrackId', 'visualParent'] - if (avalon_asset['data']['parents'] != data['parents'] or - avalon_asset['silo'] != silo): + elif (avalon_asset['data']['parents'] != data['parents'] or + avalon_asset['silo'] != silo): raise ValueError('In Avalon DB already exists entity with name "{0}"'.format(name)) elif avalon_asset['name'] != entity['name']: - raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please create new asset'.format(avalon_asset['name'], name)) + raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please set name back'.format(avalon_asset['name'], name)) elif avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']: old_path = "/".join(avalon_asset['data']['parents']) new_path = "/".join(data['parents']) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index 4854a36f8c..0ed231c625 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -12,12 +12,17 @@ from pype.ftrack import ftrack_utils class Sync_to_Avalon(BaseEvent): def launch(self, session, entities, event): + self.ca_mongoid = 'avalon_mongo_id' + # If mongo_id textfield has changed: RETURN! + # - infinite loop for ent in event['data']['entities']: - if self.ca_mongoid in ent['keys']: - return False + if 'keys' in ent: + if self.ca_mongoid in ent['keys']: + return self.proj = None + # get project for entity in entities: try: base_proj = entity['link'][0] @@ -26,13 +31,24 @@ class Sync_to_Avalon(BaseEvent): self.proj = session.get(base_proj['type'], base_proj['id']) break - if self.proj is None: - return False + # check if project is set to auto-sync + if (self.proj is None or + 'avalon_auto_sync' not in self.proj['custom_attributes'] or + self.proj['custom_attributes']['avalon_auto_sync'] is False): + return - os.environ["AVALON_PROJECT"] = self.proj['full_name'] + # check if project have Custom Attribute 'avalon_mongo_id' + if self.ca_mongoid not in self.proj['custom_attributes']: + message = "Custom attribute '{}' for 'Project' is not created or don't have set permissions for API".format(self.ca_mongoid) + self.log.warning(message) + self.show_message(event, message, False) + return self.projectId = self.proj['custom_attributes'][self.ca_mongoid] + os.environ["AVALON_PROJECT"] = self.proj['full_name'] + + # get avalon project if possible io.install() try: self.avalon_project = io.find_one({"_id": ObjectId(self.projectId)}) @@ -40,13 +56,13 @@ class Sync_to_Avalon(BaseEvent): self.avalon_project = None importEntities = [] - if self.avalon_project is None: self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) if self.avalon_project is None: importEntities.append(self.proj) else: self.projectId = self.avalon_project['_id'] + io.uninstall() for entity in entities: @@ -56,7 +72,8 @@ class Sync_to_Avalon(BaseEvent): try: mongo_id = entity['custom_attributes'][self.ca_mongoid] except: - message = "Please run 'Create Attributes' action or create custom attribute 'avalon_mongo_id' manually for {}".format(entity.entity_type) + message = "Custom attribute '{}' for '{}' is not created or don't have set permissions for API".format(self.ca_mongoid, entity.entity_type) + self.log.warning(message) self.show_message(event, message, False) return @@ -69,60 +86,37 @@ class Sync_to_Avalon(BaseEvent): self.setAvalonAttributes() io.install() + try: + for entity in importEntities: + self.importToAvalon(session, entity) + session.commit() - for entity in importEntities: - self.importToAvalon(entity) + except ValueError as ve: + message = str(ve) + self.show_message(event, message, False) + self.log.warning(message) + + except Exception as e: + message = str(e) + ftrack_message = "SyncToAvalon event ended with unexpected error please check log file for more information." + self.show_message(event, ftrack_message, False) + self.log.error(message) io.uninstall() - session.commit() + return - if message != "": - self.show_message(event, message, False) + def importToAvalon(self, session, entity): + if self.ca_mongoid not in entity['custom_attributes']: + raise ValueError("Custom attribute '{}' for '{}' is not created or don't have set permissions for API".format(self.ca_mongoid, entity['name'])) - return True - - def importToAvalon(self, entity): - data = {} + ftrack_utils.avalon_check_name(entity) entity_type = entity.entity_type - type = 'asset' - name = entity['name'] - silo = 'Film' if entity_type in ['Project']: type = 'project' name = entity['full_name'] - data['code'] = entity['name'] - elif entity_type in ['AssetBuild', 'Library']: - silo = 'Assets' - - os.environ["AVALON_ASSET"] = name - os.environ["AVALON_SILO"] = silo - - data['ftrackId'] = entity['id'] - data['entityType'] = entity_type - - for cust_attr in self.custom_attributes: - key = cust_attr['key'] - if cust_attr['entity_type'].lower() in ['asset']: - data[key] = entity['custom_attributes'][key] - - elif cust_attr['entity_type'].lower() in ['show'] and entity_type.lower() == 'project': - data[key] = entity['custom_attributes'][key] - - elif cust_attr['entity_type'].lower() in ['task'] and entity_type.lower() != 'project': - # Put space between capitals (e.g. 'AssetBuild' -> 'Asset Build') - entity_type_full = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) - # Get object id of entity type - ent_obj_type_id = self.session.query('ObjectType where name is "{}"'.format(entity_type_full)).one()['id'] - - if cust_attr['object_type_id'] == ent_obj_type_id: - data[key] = entity['custom_attributes'][key] - - mongo_id = entity['custom_attributes'][self.ca_mongoid] - - if entity_type in ['Project']: config = ftrack_utils.get_config(entity) template = lib.get_avalon_project_template_schema() @@ -130,8 +124,12 @@ class Sync_to_Avalon(BaseEvent): inventory.save(name, config, template) self.avalon_project = io.find_one({'type': 'project', 'name': name}) + elif self.avalon_project['name'] != name: + raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(self.avalon_project['name'], name)) + self.projectId = self.avalon_project['_id'] - data['code'] = entity['name'] + + data = ftrack_utils.get_data(self, entity, session,self.custom_attributes) io.update_many( {"_id": ObjectId(self.projectId)}, @@ -140,48 +138,30 @@ class Sync_to_Avalon(BaseEvent): 'config':config, 'data':data, }}) - try: - entity['custom_attributes'][self.ca_mongoid] = str(self.projectId) - except Exception as e: - self.log.error(e) + + entity['custom_attributes'][self.ca_mongoid] = str(self.projectId) + return - if self.avalon_project is None: - self.importToAvalon(self.proj) + self.importToAvalon(session, self.proj) - tasks = [] - for child in entity['children']: - if child.entity_type in ['Task']: - tasks.append(child['name']) + data = ftrack_utils.get_data(self, entity, session,self.custom_attributes) - folderStruct = [] - parentId = None + # return if entity is silo + if len(data['parents']) == 0: + return + else: + silo = data['parents'][0] - parents = [] - for i in range(1, len(entity['link'])-1): - tmp_type = entity['link'][i]['type'] - tmp_id = entity['link'][i]['id'] - tmp = self.session.get(tmp_type, tmp_id) - parents.append(tmp) + name = entity['name'] - for parent in parents: - parname = self.checkName(parent['name']) - folderStruct.append(parname) - avalonAarent = io.find_one({'type': 'asset', 'name': parname}) - if parent['parent'].entity_type != 'project' and avalonAarent is None: - self.importToAvalon(parent) - parentId = io.find_one({'type': 'asset', 'name': parname})['_id'] - - hierarchy = os.path.sep.join(folderStruct) - - data['tasks'] = tasks - if parentId is not None: - data['parents'] = folderStruct - data['visualParent'] = parentId - data['hierarchy'] = hierarchy + os.environ["AVALON_ASSET"] = name + os.environ['AVALON_SILO'] = silo avalon_asset = None + # existence of this custom attr is already checked + mongo_id = entity['custom_attributes'][self.ca_mongoid] if mongo_id is not "": avalon_asset = io.find_one({'_id': ObjectId(mongo_id)}) @@ -190,15 +170,17 @@ class Sync_to_Avalon(BaseEvent): avalon_asset = io.find_one({'type': 'asset', 'name': name}) if avalon_asset is None: mongo_id = inventory.create_asset(name, silo, data, ObjectId(self.projectId)) - else: - if name != avalon_asset['name']: - string = "'{}->{}'".format(name, avalon_asset['name']) - if entity_type in ['Shot','AssetBuild']: - self.nameShotAsset.append(string) - mongo_id = inventory.create_asset(name, silo, data, ObjectId(self.projectId)) - else: - self.nameChanged.append(string) - return + # Raise error if it seems to be different ent. with same name + elif (avalon_asset['data']['parents'] != data['parents'] or + avalon_asset['silo'] != silo): + raise ValueError('In Avalon DB already exists entity with name "{0}"'.format(name)) + elif avalon_asset['name'] != entity['name']: + raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please set name back'.format(avalon_asset['name'], name)) + elif avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']: + old_path = "/".join(avalon_asset['data']['parents']) + new_path = "/".join(data['parents']) + raise ValueError('You can\'t move with entities. Entity "{}" was moved from "{}" to "{}" , avalon DB won\'t work properly'.format(avalon_asset['name'], old_path, new_path)) + io.update_many( {"_id": ObjectId(mongo_id)}, @@ -206,21 +188,9 @@ class Sync_to_Avalon(BaseEvent): 'name':name, 'silo':silo, 'data':data, - 'parent': self.projectId}}) + 'parent': ObjectId(self.projectId)}}) - try: - entity['custom_attributes'][self.ca_mongoid] = str(mongo_id) - except Exception as e: - self.log.error(e) - - - def checkName(self, input_name): - if input_name.find(" ") == -1: - name = input_name - else: - name = input_name.replace(" ", "-") - print("Name of {} was changed to {}".format(input_name, name)) - return name + entity['custom_attributes'][self.ca_mongoid] = str(mongo_id) def setAvalonAttributes(self): self.custom_attributes = [] diff --git a/pype/ftrack/events/test_event.py b/pype/ftrack/events/event_test.py similarity index 90% rename from pype/ftrack/events/test_event.py rename to pype/ftrack/events/event_test.py index c2586aa666..128e1ad197 100644 --- a/pype/ftrack/events/test_event.py +++ b/pype/ftrack/events/event_test.py @@ -11,7 +11,7 @@ class Test_Event(BaseEvent): '''just a testing event''' - # self.show_message(event,"test",True) + # self.log.info(event) return True diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index d0ecd53192..a440d93fc4 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -17,18 +17,15 @@ from app.api import ( class BaseEvent(object): - '''Custom Action base class + '''Custom Event base class - `label` a descriptive string identifing your action. + BaseEvent is based on ftrack.update event + - get entities from event - `varaint` To group actions together, give them the same - label and specify a unique variant per action. + If want to use different event base + - override register and *optional _translate_event method - `identifier` a unique identifier for your action. - - `description` a verbose descriptive text for you action - - ''' + ''' def __init__(self, session): '''Expects a ftrack_api.Session instance''' @@ -46,7 +43,7 @@ class BaseEvent(object): '''Registers the event, subscribing the the discover and launch topics.''' self.session.event_hub.subscribe('topic=ftrack.update', self._launch) - self.log.info("----- event - <" + self.__class__.__name__ + "> - Has been registered -----") + self.log.info("Event '{}' - Registered successfully".format(self.__class__.__name__)) def _translate_event(self, session, event): '''Return *event* translated structure to be used with the API.''' @@ -91,25 +88,18 @@ class BaseEvent(object): ) def _launch(self, event): + + self.session.reset() + args = self._translate_event( self.session, event ) - # TODO REMOVE THIS - ONLY FOR TEST PROJECT - for a in args[0]: - try: - if (a['project']['name'] != 'eventproj'): - return True - except: - continue - - response = self.launch( + self.launch( self.session, *args ) - return self._handle_result( - self.session, response, *args - ) + return def launch(self, session, entities, event): '''Callback method for the custom action. @@ -133,6 +123,14 @@ class BaseEvent(object): raise NotImplementedError() def show_message(self, event, input_message, result = False): + """ + Shows message to user who triggered event + - event - just source of user id + - input_message - message that is shown to user + - result - changes color of message (based on ftrack settings) + - True = Violet + - False = Red + """ if not isinstance(result, bool): result = False @@ -147,38 +145,10 @@ class BaseEvent(object): topic='ftrack.action.trigger-user-interface', data=dict( type='message', - success=False, + success=result, message=message ), target='applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) ), on_error='ignore' ) - - def _handle_result(self, session, result, entities, event): - '''Validate the returned result from the action callback''' - if isinstance(result, bool): - result = { - 'success': result, - 'message': ( - '{0} launched successfully.'.format( - self.__class__.__name__ - ) - ) - } - - elif isinstance(result, dict): - for key in ('success', 'message'): - if key in result: - continue - - raise KeyError( - 'Missing required key: {0}.'.format(key) - ) - - else: - self.log.error( - 'Invalid result type must be bool or dictionary!' - ) - - return result diff --git a/pype/ftrack/ftrack_utils.py b/pype/ftrack/ftrack_utils.py index 66d739829b..caaeb6c707 100644 --- a/pype/ftrack/ftrack_utils.py +++ b/pype/ftrack/ftrack_utils.py @@ -62,7 +62,7 @@ def get_data(parent, entity, session, custom_attributes): for parent in parents: parentId = io.find_one({'type': 'asset', 'name': parName})['_id'] if parent['parent'].entity_type != 'project' and parentId is None: - parent.importToAvalon(parent) + parent.importToAvalon(session, parent) parentId = io.find_one({'type': 'asset', 'name': parName})['_id'] hierarchy = os.path.sep.join(folderStruct)