diff --git a/pype/ftrack/actions/action_Apps.py b/pype/ftrack/actions/action_Apps.py index 70461d6ac7..0e79b4c0d4 100644 --- a/pype/ftrack/actions/action_Apps.py +++ b/pype/ftrack/actions/action_Apps.py @@ -6,22 +6,15 @@ from ftrack_action_handler.appaction import AppAction from avalon import io, lib -os.environ['AVALON_PROJECTS'] = 'tmp' -io.install() -projects = sorted(io.projects(), key=lambda x: x['name']) -io.uninstall() - -# Temporary -s = ftrack_api.Session( - server_url="https://pype.ftrackapp.com", - api_key="4e01eda0-24b3-4451-8e01-70edc03286be", - api_user="jakub.trllo" -) - def register(session): + + os.environ['AVALON_PROJECTS'] = 'tmp' + io.install() + projects = sorted(io.projects(), key=lambda x: x['name']) + io.uninstall() + apps=[] actions = [] - icon = None for project in projects: os.environ['AVALON_PROJECT'] = project['name'] @@ -29,16 +22,19 @@ def register(session): if app not in apps: apps.append(app) + # TODO get right icons for app in apps: + name = app['name'].split("_")[0] + variant = app['name'].split("_")[1] + label = app['label'] + executable = toml.load(lib.which_app(app['name']))['executable'] + icon = None + if 'nuke' in app['name']: icon = "https://mbtskoudsalg.com/images/nuke-icon-png-2.png" + label = "Nuke" elif 'maya' in app['name']: icon = "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Maya-icon.png" - else: - icon = None + label = "Autodesk Maya" - AppAction(session, app['label'], app['name'], icon).register() - - session.event_hub.wait() - -register(s) + AppAction(session, label, name, executable, variant, icon).register() diff --git a/pype/ftrack/actions/action_createCustomAttributes.py b/pype/ftrack/actions/action_createCustomAttributes.py new file mode 100644 index 0000000000..d0d538d9de --- /dev/null +++ b/pype/ftrack/actions/action_createCustomAttributes.py @@ -0,0 +1,257 @@ +# :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, lib +from avalon.vendor import toml + +class AvalonIdAttribute(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'avalon.id.attribute' + #: Action label. + label = 'Create Avalon Attribute' + #: Action description. + description = 'Creates Avalon/Mongo ID for double check' + + 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"] + os.environ["AVALON_ASSET"] = entityProj['full_name'] + + # 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"} + + # --- Create project and assets in Avalon --- + io.install() + # 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) + else: + io.update_many({'type': 'project','name': entityProj['full_name']}, + {'$set':{'config':config}}) + + # Store info about project (FtrackId) + io.update_many({'type': 'project','name': entityProj['full_name']}, + {'$set':{'data':{'ftrackId':entityProj['id'],'entityType':entityProj.entity_type}}}) + + # 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 = AvalonIdAttribute(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() + register(session) + + # Wait for events + logging.info( + 'Registered actions and listening for events. Use Ctrl-C to abort.' + ) + session.event_hub.wait() + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/pype/ftrack/actions/action_killRunningJobs.py b/pype/ftrack/actions/action_killRunningJobs.py index 44058a02d6..20bd2eee38 100644 --- a/pype/ftrack/actions/action_killRunningJobs.py +++ b/pype/ftrack/actions/action_killRunningJobs.py @@ -95,11 +95,8 @@ def main(arguments=None): # 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", - api_user="jakub.trllo" - ) + session = ftrack_api.Session() + register(session) # Wait for events diff --git a/pype/ftrack/actions/action_sTa_opt.py b/pype/ftrack/actions/action_sTa_opt.py deleted file mode 100644 index 1fb75a03b5..0000000000 --- a/pype/ftrack/actions/action_sTa_opt.py +++ /dev/null @@ -1,154 +0,0 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack -import sys -import argparse -import logging -import collections -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 TestAction(BaseAction): - '''Edit meta data action.''' - - #: Action identifier. - identifier = 'test.action' - - #: Action label. - label = 'Test action' - - #: Action description. - description = 'Test action' - - def validate_selection(self, session, entities): - '''Return if *entities* is a valid selection.''' - 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 launch(self, session, entities, event): - - def fromTop(index, max, inEnt): - output=dict() - entity = session.get(inEnt[index]['type'], inEnt[index]['id']) - tasks=[] - for e in entity['children']: - if e.entity_type in ['Task']: - tasks.append(e['name']) - # Get info about all parents - if index < max: - output = {'name': entity['name'], 'tasks': tasks, 'childrens': fromTop(index+1, max, inEnt)} - # Get info about all childrens - else: - childrens = [] - for e in entity['children']: - if e.entity_type not in ['Task']: - childrens.append(toBottom(e)) - output = {'name': entity['name'], 'tasks': tasks, 'childrens': childrens} - return output - - def toBottom(entity): - tasks = [] - childrens = [] - # If entity have childrens do: - if entity['children']: - # Get all tasks - for e in entity['children']: - if e.entity_type in ['Task']: - tasks.append(e['name']) - # Get childrens of children - for e in entity['children']: - if e.entity_type not in ['Task']: - childrens.append(toBottom(e)) - return {'name': entity['name'], 'tasks': tasks, 'childrens': childrens} - - def project(proj): - type = 'project' - addProjectToDatabase() - projId = getIdFromDb() - if len(proj['childrens'] > 0): - childrens = proj['childrens'] - test(childrens, None, projId) - - def test(childrens, parentId, projId): - type = 'asset' - for child in childrens: - silo = 'Assets' if child['type'] in ['AssetBuild', 'Library'] else 'Film' - addtodatabase() - newId = fromdatabase() - if len(child['childrens']) > 0: - test(child, newId, projId) - - - for entity in entities: - entity_type, entity_id = entity - entity = session.get(entity_type, entity_id) - max = len(entity['link']) - 1 - out = fromTop(0, max, entity['link']) - print(100*"_") - print(out) - - return True - - -def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - - if not isinstance(session, ftrack_api.session.Session): - return - - action_handler = TestAction(session) - action_handler.register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session( - server_url="https://pype.ftrackapp.com", - api_key="4e01eda0-24b3-4451-8e01-70edc03286be", - api_user="jakub.trllo" - ) - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) diff --git a/pype/ftrack/actions/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index 8b9061045e..dd8b096ca8 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -90,8 +90,8 @@ class SyncToAvalon(BaseAction): # --- Create project and assets in Avalon --- io.install() # If project don't exists -> ELSE - if (io.find_one( - {'type': 'project', 'name': entityProj['full_name']}) is None): + if (io.find_one({'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']}, @@ -245,10 +245,7 @@ def main(arguments=None): # 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", - ) + session = ftrack_api.Session() register(session) # Wait for events diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index 8cab6924da..7123422cc8 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -37,7 +37,7 @@ class TestAction(BaseAction): return self.validate_selection(session, entities) def launch(self, session, entities, event): - + return True @@ -76,11 +76,7 @@ def main(arguments=None): # 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", - api_user="jakub.trllo" - ) + session = ftrack_api.Session() register(session) # Wait for events diff --git a/pype/ftrack/actions/commercial.toml b/pype/ftrack/actions/commercial.toml deleted file mode 100644 index b24db4d2d1..0000000000 --- a/pype/ftrack/actions/commercial.toml +++ /dev/null @@ -1,30 +0,0 @@ -schema = "avalon-core:config-1.0" -[[tasks]] -name = "model" - -[[tasks]] -name = "render" - -[[tasks]] -name = "animate" - -[[tasks]] -name = "rig" - -[[tasks]] -name = "lookdev" - -[[tasks]] -name = "layout" - -[[apps]] -name = "maya2018" -label = "Autodesk Maya 2018" - -[[apps]] -name = "nuke11" -label = "The Foundry Nuke 11.0" - -[template] -work = "{root}/{project}/{silo}/{asset}/work/{task}/{app}" -publish = "{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}" diff --git a/pype/ftrack/actions/dev.toml b/pype/ftrack/actions/dev.toml deleted file mode 100644 index b24db4d2d1..0000000000 --- a/pype/ftrack/actions/dev.toml +++ /dev/null @@ -1,30 +0,0 @@ -schema = "avalon-core:config-1.0" -[[tasks]] -name = "model" - -[[tasks]] -name = "render" - -[[tasks]] -name = "animate" - -[[tasks]] -name = "rig" - -[[tasks]] -name = "lookdev" - -[[tasks]] -name = "layout" - -[[apps]] -name = "maya2018" -label = "Autodesk Maya 2018" - -[[apps]] -name = "nuke11" -label = "The Foundry Nuke 11.0" - -[template] -work = "{root}/{project}/{silo}/{asset}/work/{task}/{app}" -publish = "{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}" diff --git a/pype/ftrack/actions/dogz.toml b/pype/ftrack/actions/dogz.toml deleted file mode 100644 index 7c7423ce3b..0000000000 --- a/pype/ftrack/actions/dogz.toml +++ /dev/null @@ -1,30 +0,0 @@ -schema = "avalon-core:config-1.0" -[[tasks]] -name = "model" - -[[tasks]] -name = "render" - -[[tasks]] -name = "animate" - -[[tasks]] -name = "rig" - -[[tasks]] -name = "lookdev" - -[[tasks]] -name = "layout" - -[[apps]] -name = "maya2018" -label = "Autodesk Maya 2018" - -[[apps]] -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}" diff --git a/pype/ftrack/actions/t.py b/pype/ftrack/actions/t.py new file mode 100644 index 0000000000..52bb7b0a16 --- /dev/null +++ b/pype/ftrack/actions/t.py @@ -0,0 +1,44 @@ + import model +# model + +name = model.data(index, "name") + +# Get the action +Action = next((a for a in self._registered_actions if a.name == name), + None) +assert Action, "No action found" +action = Action() + +# Run the action within current session +self.log("Running action: %s" % name, level=INFO) +popen = action.process(api.Session.copy()) +# Action might return popen that pipes stdout +# in which case we listen for it. +process = {} +if popen and hasattr(popen, "stdout") and popen.stdout is not None: + + class Thread(QtCore.QThread): + messaged = Signal(str) + + def run(self): + for line in lib.stream(process["popen"].stdout): + self.messaged.emit(line.rstrip()) + self.messaged.emit("%s killed." % process["name"]) + + thread = Thread() + thread.messaged.connect( + lambda line: terminal.log(line, terminal.INFO) + ) + + process.update({ + "name": name, + "action": action, + "thread": thread, + "popen": popen + }) + + self._processes.append(process) + + thread.start() + +# return process diff --git a/pype/ftrack/actions/vfx.toml b/pype/ftrack/actions/vfx.toml deleted file mode 100644 index 605c2e5a5c..0000000000 --- a/pype/ftrack/actions/vfx.toml +++ /dev/null @@ -1,14 +0,0 @@ -schema = "avalon-core:config-1.0" -[[tasks]] -name = "" - -[[apps]] -name = "maya2018" -label = "Autodesk Maya 2018" - -[[apps]] -name = "nuke11" -label = "The Foundry Nuke 11.0" - -[template] -work = "" diff --git a/pype/vendor/ftrack_action_handler/appaction.py b/pype/vendor/ftrack_action_handler/appaction.py index a1c19c1f3c..5b71bbddd3 100644 --- a/pype/vendor/ftrack_action_handler/appaction.py +++ b/pype/vendor/ftrack_action_handler/appaction.py @@ -18,7 +18,7 @@ class AppAction(object): - icon in ftrack ''' - def __init__(self, session, label, name, icon=None, variant=None, description=None): + def __init__(self, session, label, name, executable, variant=None, icon=None, description=None): '''Expects a ftrack_api.Session instance''' self.logger = logging.getLogger( @@ -29,12 +29,15 @@ class AppAction(object): raise ValueError('Action missing label.') elif name is None: raise ValueError('Action missing identifier.') + elif executable is None: + raise ValueError('Action missing executable.') self._session = session self.label = label self.identifier = name - self.icon = icon + self.executable = executable self.variant = variant + self.icon = icon self.description = description @@ -118,7 +121,7 @@ class AppAction(object): else: apps = [] for app in project['config']['apps']: - apps.append(app['name']) + apps.append(app['name'].split("_")[0]) if self.identifier not in apps: return False @@ -208,6 +211,10 @@ class AppAction(object): # TODO Delete this line print("Action - {0} ({1}) - just started".format(self.label, self.identifier)) + # lib.launch(executable=self.executable, + # args=["-u", "-m", "avalon.tools.projectmanager", + # session['AVALON_PROJECT']]) + # Get path to execute st_temp_path = os.environ['PYPE_STUDIO_TEMPLATES'] os_plat = platform.system().lower() @@ -218,11 +225,13 @@ class AppAction(object): execfile = None for ext in os.environ["PATHEXT"].split(os.pathsep): - fpath = os.path.join(path.strip('"'), self.identifier + ext) + fpath = os.path.join(path.strip('"'), self.executable + ext) if os.path.isfile(fpath) and os.access(fpath, os.X_OK): execfile = fpath break + + if execfile is not None: os.startfile(execfile) else: