diff --git a/pype/ftrack/actions/action_Apps.py b/pype/ftrack/actions/action_Apps.py index d869a764a6..8efef6434a 100644 --- a/pype/ftrack/actions/action_Apps.py +++ b/pype/ftrack/actions/action_Apps.py @@ -5,7 +5,7 @@ from ftrack_action_handler.appaction import AppAction from avalon import io -os.environ['AVALON_PROJECTS']='tmp' +os.environ['AVALON_PROJECTS'] = 'tmp' io.install() projects = sorted(io.projects(), key=lambda x: x['name']) io.uninstall() @@ -18,14 +18,17 @@ s = ftrack_api.Session( ) def register(session): + apps=[] actions = [] for project in projects: - os.environ['AVALON_PROJECTS'] = project['name'] + os.environ['AVALON_PROJECT'] = project['name'] for app in project['config']['apps']: - actions.append(AppAction(session, project['name'], app['label'], app['name'])) - for action in actions: - action.register() - print(actions) + if app not in apps: + apps.append(app) + + for app in apps: + AppAction(session, app['label'], app['name']).register() + session.event_hub.wait() diff --git a/pype/ftrack/actions/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index 81c88ab651..b027a0f4be 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -8,21 +8,20 @@ import json import ftrack_api from ftrack_action_handler.action import BaseAction -from avalon import io, inventory, schema +from avalon import io, inventory, lib from avalon.vendor import toml - class SyncToAvalon(BaseAction): '''Edit meta data action.''' #: Action identifier. identifier = 'sync.to.avalon' - #: Action label. label = 'SyncToAvalon' - #: Action description. description = 'Send data from Ftrack to Avalon' + #: Action icon. + icon = 'https://cdn1.iconfinder.com/data/icons/hawcons/32/699650-icon-92-inbox-download-512.png' def validate_selection(self, session, entities): '''Return if *entities* is a valid selection.''' @@ -67,24 +66,37 @@ class SyncToAvalon(BaseAction): # set AVALON_PROJECT env os.environ["AVALON_PROJECT"] = entityProj["full_name"] - # get schema of project TODO read different schemas based on project type + # Get apps from Ftrack / TODO Exceptions?!!! + apps = [] + for app in entityProj['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)) + + # Set project Config + config = { + 'schema': 'avalon-core:config-1.0', + 'tasks': [{'name': ''}], + 'apps': apps, + 'template': {'work': '','publish':''} + } + + # Set project template template = {"schema": "avalon-core:inventory-1.0"} - schema = entityProj['project_schema']['name'] - fname = os.path.join(os.path.dirname( - os.path.realpath(__file__)), - (schema + '.toml')) - try: - with open(fname) as f: - config = toml.load(f) - except IOError: - raise # --- Create project and assets in Avalon --- + os.environ['AVALON_ASSET'] = entityProj['full_name'] + io.install() - # Check if project exists -> Create project + # If project don't exists -> ELSE if (io.find_one( - {"type": "project", "name": entityProj["full_name"]}) is None): - inventory.save(entityProj["full_name"], config, template) + {'type': 'project', 'name': entityProj['full_name']}) is None): + inventory.save(entityProj['full_name'], config, template) + else: + io.update_many({'type': 'project','name': entityProj['full_name']}, + {'$set':{'config':config}}) # Store project Id projectId = io.find_one({"type": "project", "name": entityProj["full_name"]})["_id"] @@ -105,25 +117,34 @@ class SyncToAvalon(BaseAction): folderStruct = [] parentId = None data = {'visualParent': parentId, 'parents': folderStruct, - 'ftrackId': None, 'entityType': None} + 'tasks':None, 'ftrackId': None, 'entityType': None} for asset in assets: - os.environ['AVALON_ASSET'] = asset['name'] data.update({'ftrackId': asset['ftrackId'], 'entityType': asset['type']}) - if (io.find_one({'type': 'asset', 'name': asset['name']}) is None): - inventory.create_asset(asset['name'], silo, data, projectId) - print("Asset "+asset['name']+" created") + # Get tasks of each asset + assetEnt = session.get('TypedContext', asset['ftrackId']) + tasks = [] + for child in assetEnt['children']: + if child.entity_type in ['Task']: + tasks.append(child['name']) + data.update({'tasks': tasks}) + if (io.find_one({'type': 'asset', 'name': asset['name']}) is None): + # Create asset in DB + inventory.create_asset(asset['name'], silo, data, projectId) + print("Asset "+asset['name']+" - created") else: + io.update_many({'type': 'asset','name': asset['name']}, + {'$set':{'data':data}}) # TODO check if is asset in same folder!!! ???? FEATURE FOR FUTURE - print("Asset "+asset["name"]+" already exist") + print("Asset "+asset["name"]+" - already exist") parentId = io.find_one({'type': 'asset', 'name': asset['name']})['_id'] - data.update({'visualParent': parentId, 'parents': folderStruct}) folderStruct.append(asset['name']) + # Set custom attribute to avalon/mongo id of entity (parentID is last) if custAttrName in entity['custom_attributes'] and entity['custom_attributes'][custAttrName] is '': entity['custom_attributes'][custAttrName] = str(parentId) @@ -144,6 +165,7 @@ class SyncToAvalon(BaseAction): }) try: + print("action <" + self.__class__.__name__ + "> is running") #TODO It's better to have these env set, are they used anywhere? os.environ['AVALON_PROJECTS'] = "tmp" os.environ['AVALON_ASSET'] = "tmp" @@ -173,7 +195,7 @@ class SyncToAvalon(BaseAction): session.commit() print('Synchronization to Avalon was successfull!') - except(e): + except Exception as e: job['status'] = 'failed' print('During synchronization to Avalon went something wrong!') print(e) @@ -192,6 +214,7 @@ def register(session, **kw): action_handler = SyncToAvalon(session) action_handler.register() + print("----- action - <" + action_handler.__class__.__name__ + "> - Has been registered -----") def main(arguments=None): diff --git a/pype/ftrack/actions/action_syncToAvalon_v2.py b/pype/ftrack/actions/action_syncToAvalon_v2.py deleted file mode 100644 index 0360223f9c..0000000000 --- a/pype/ftrack/actions/action_syncToAvalon_v2.py +++ /dev/null @@ -1,247 +0,0 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack -import sys -import argparse -import logging -import os -import json -import ftrack_api -from ftrack_action_handler.action import BaseAction - -from avalon import io, inventory, schema -from avalon.vendor import toml - - -class SyncToAvalon(BaseAction): - '''Edit meta data action.''' - - #: Action identifier. - identifier = 'sync.to.avalon' - #: Action label. - label = 'SyncToAvalon' - #: Action description. - description = 'Send data from Ftrack to Avalon' - #: Action icon. - icon = 'https://cdn1.iconfinder.com/data/icons/hawcons/32/699650-icon-92-inbox-download-512.png' - - def validate_selection(self, session, entities): - '''Return if *entities* is a valid selection.''' - # if (len(entities) != 1): - # # If entities contains more than one item return early since - # # metadata cannot be edited for several entites at the same time. - # return False - # entity_type, entity_id = entities[0] - # if ( - # entity_type not in session.types - # ): - # # Return False if the target entity does not have a metadata - # # attribute. - # return False - pass - return True - - def discover(self, session, entities, event): - '''Return True if action is valid.''' - - self.logger.info('Got selection: {0}'.format(entities)) - return self.validate_selection(session, entities) - - - def importToAvalon(self, session, entity): - eLinks = [] - custAttrName = 'avalon_mongo_id' - # TODO read from file, which data are in scope??? - # get needed info of entity and all parents - for e in entity['link']: - tmp = session.get(e['type'], e['id']) - if e['name'].find(" ") == -1: - name = e['name'] - else: - name = e['name'].replace(" ", "-") - print("Name of "+tmp.entity_type+" - "+e['name']+" was changed to "+name) - - eLinks.append({"type": tmp.entity_type, "name": name, "ftrackId": tmp['id']}) - - entityProj = session.get(eLinks[0]['type'], eLinks[0]['ftrackId']) - - # set AVALON_PROJECT env - os.environ["AVALON_PROJECT"] = entityProj["full_name"] - - # get schema of project TODO read different schemas based on project type - template = {"schema": "avalon-core:inventory-1.0"} - schema = entityProj['project_schema']['name'] - fname = os.path.join(os.path.dirname( - os.path.realpath(__file__)), - (schema + '.toml')) - try: - with open(fname) as f: - config = toml.load(f) - except IOError: - raise - - # --- Create project and assets in Avalon --- - io.install() - # Check if project exists -> Create project - if (io.find_one( - {"type": "project", "name": entityProj["full_name"]}) is None): - inventory.save(entityProj["full_name"], config, template) - - # Store project Id - projectId = io.find_one({"type": "project", "name": entityProj["full_name"]})["_id"] - if custAttrName in entityProj['custom_attributes'] and entityProj['custom_attributes'][custAttrName] is '': - entityProj['custom_attributes'][custAttrName] = str(projectId) - - # If entity is Project or have only 1 entity kill action - if (len(eLinks) > 1) and not (eLinks[-1]['type'] in ['Project']): - - # TODO how to check if entity is Asset Library or AssetBuild? - silo = 'Assets' if eLinks[-1]['type'] in ['AssetBuild', 'Library'] else 'Film' - os.environ['AVALON_SILO'] = silo - # Create Assets - assets = [] - for i in range(1, len(eLinks)): - assets.append(eLinks[i]) - - folderStruct = [] - parentId = None - data = {'visualParent': parentId, 'parents': folderStruct, - 'tasks':None, 'ftrackId': None, 'entityType': None} - - for asset in assets: - os.environ['AVALON_ASSET'] = asset['name'] - data.update({'ftrackId': asset['ftrackId'], 'entityType': asset['type']}) - # Get tasks of each asset - assetEnt = session.get('TypedContext', asset['ftrackId']) - tasks = [] - for child in assetEnt['children']: - if child.entity_type in ['Task']: - tasks.append(child['name']) - data.update({'tasks': tasks}) - - if (io.find_one({'type': 'asset', 'name': asset['name']}) is None): - # Create asset in DB - inventory.create_asset(asset['name'], silo, data, projectId) - print("Asset "+asset['name']+" - created") - else: - io.update_many({'type': 'asset','name': asset['name']}, - {'$set':{'data':data}}) - # TODO check if is asset in same folder!!! ???? FEATURE FOR FUTURE - print("Asset "+asset["name"]+" - already exist") - - parentId = io.find_one({'type': 'asset', 'name': asset['name']})['_id'] - data.update({'visualParent': parentId, 'parents': folderStruct}) - folderStruct.append(asset['name']) - - - # Set custom attribute to avalon/mongo id of entity (parentID is last) - if custAttrName in entity['custom_attributes'] and entity['custom_attributes'][custAttrName] is '': - entity['custom_attributes'][custAttrName] = str(parentId) - - io.uninstall() - - def launch(self, session, entities, event): - # JOB SETTINGS - userId = event['source']['user']['id'] - user = session.query('User where id is ' + userId).one() - - job = session.create('Job', { - 'user': user, - 'status': 'running', - 'data': json.dumps({ - 'description': 'Synch Ftrack to Avalon.' - }) - }) - - try: - print("action <" + self.__class__.__name__ + "> is running") - #TODO It's better to have these env set, are they used anywhere? - os.environ['AVALON_PROJECTS'] = "tmp" - os.environ['AVALON_ASSET'] = "tmp" - os.environ['AVALON_SILO'] = "tmp" - importable = [] - - def getShotAsset(entity): - if not (entity.entity_type in ['Task']): - if entity not in importable: - importable.append(entity) - - if entity['children']: - childrens = entity['children'] - for child in childrens: - getShotAsset(child) - - # get all entities separately - for entity in entities: - entity_type, entity_id = entity - act_ent = session.get(entity_type, entity_id) - getShotAsset(act_ent) - - for e in importable: - self.importToAvalon(session, e) - - job['status'] = 'done' - session.commit() - - print('Synchronization to Avalon was successfull!') - except Exception as e: - job['status'] = 'failed' - print('During synchronization to Avalon went something wrong!') - print(e) - - return True - - -def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - - # Validate that session is an instance of ftrack_api.Session. If not, - # assume that register is being called from an old or incompatible API and - # return without doing anything. - if not isinstance(session, ftrack_api.session.Session): - return - - action_handler = SyncToAvalon(session) - action_handler.register() - print("----- action - <" + action_handler.__class__.__name__ + "> - Has been registered -----") - - -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( - server_url="https://pype.ftrackapp.com", - api_key="4e01eda0-24b3-4451-8e01-70edc03286be", - ) - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) diff --git a/pype/ftrack/actions/vfx.toml b/pype/ftrack/actions/vfx.toml index 7c7423ce3b..605c2e5a5c 100644 --- a/pype/ftrack/actions/vfx.toml +++ b/pype/ftrack/actions/vfx.toml @@ -1,21 +1,6 @@ schema = "avalon-core:config-1.0" [[tasks]] -name = "model" - -[[tasks]] -name = "render" - -[[tasks]] -name = "animate" - -[[tasks]] -name = "rig" - -[[tasks]] -name = "lookdev" - -[[tasks]] -name = "layout" +name = "" [[apps]] name = "maya2018" @@ -26,5 +11,4 @@ name = "nuke11" label = "The Foundry Nuke 11.0" [template] -work = "{root}/{project}/{asset}/work/{task}/{app}" -publish = "{root}/{project}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}" +work = "" diff --git a/pype/vendor/ftrack_action_handler/appaction.py b/pype/vendor/ftrack_action_handler/appaction.py index b1b76c3f10..7acb554b0a 100644 --- a/pype/vendor/ftrack_action_handler/appaction.py +++ b/pype/vendor/ftrack_action_handler/appaction.py @@ -1,10 +1,10 @@ # :coding: utf-8 # :copyright: Copyright (c) 2017 ftrack - +import os import logging import getpass import ftrack_api - +from avalon import io class AppAction(object): '''Custom Action base class @@ -16,13 +16,8 @@ class AppAction(object): - a verbose descriptive text for you action - icon in ftrack ''' - label = None - variant = None - identifier = None - description = None - icon = None - def __init__(self, session, project, label, name, variant=None, description=None, icon=None): + def __init__(self, session, label, name, icon=None, variant=None, description=None): '''Expects a ftrack_api.Session instance''' self.logger = logging.getLogger( @@ -33,13 +28,13 @@ class AppAction(object): raise ValueError('Action missing label.') elif name is None: raise ValueError('Action missing identifier.') - elif project is None: - raise ValueError('Action missing project name.') self._session = session - self.project = project self.label = label self.identifier = name + self.icon = None + self.variant = None + self.description = None @property @@ -100,8 +95,37 @@ class AppAction(object): *event* the unmodified original event ''' + if len(entities) > 1: + return False - return False + entity_type, entity_id = entities[0] + entity = session.get(entity_type, entity_id) + + ''' Should return False if not TASK ?!!! ''' + # if entity.entity_type != 'Task': + # return False + + ft_project = entity['project'] + project = None + + ''' GET ONLY PROJECT INSTEAD OF ALL (io.find_one()) ''' + os.environ['AVALON_PROJECTS'] = 'tmp' + io.install() + projects = sorted(io.projects(), key=lambda x: x['name']) + io.uninstall() + for p in projects: + if p['name'] == ft_project['full_name']: + project = p + break + + os.environ['AVALON_PROJECT'] = project['name'] + apps = [] + for app in project['config']['apps']: + apps.append(app['name']) + if self.identifier not in apps: + return False + + return True def _translate_event(self, session, event): '''Return *event* translated structure to be used with the API.'''