Merge branch 'master' into pype-templates-integration

This commit is contained in:
Milan Kolar 2018-11-14 13:23:29 +01:00 committed by GitHub
commit ab02f68810
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 3212 additions and 359 deletions

View file

@ -0,0 +1,131 @@
import sys
import argparse
import logging
import getpass
import ftrack_api
from ftrack_action_handler import BaseAction
class AssetDelete(BaseAction):
'''Custom action.'''
#: Action identifier.
identifier = 'asset.delete'
#: Action label.
label = 'Asset Delete'
def discover(self, session, entities, event):
''' Validation '''
if (len(entities) != 1 or entities[0].entity_type
not in ['Shot', 'Asset Build']):
return False
return True
def interface(self, session, entities, event):
if not event['data'].get('values', {}):
entity = entities[0]
items = []
for asset in entity['assets']:
# get asset name for label
label = 'None'
if asset['name']:
label = asset['name']
items.append({
'label':label,
'name':label,
'value':False,
'type':'boolean'
})
if len(items) < 1:
return {
'success': False,
'message': 'There are no assets to delete'
}
return items
def launch(self, session, entities, event):
entity = entities[0]
# if values were set remove those items
if 'values' in event['data']:
values = event['data']['values']
# get list of assets to delete from form
to_delete = []
for key in values:
if values[key]:
to_delete.append(key)
# delete them by name
for asset in entity['assets']:
if asset['name'] in to_delete:
session.delete(asset)
try:
session.commit()
except:
session.rollback()
raise
return {
'success': True,
'message': 'Asset deleted.'
}
def register(session, **kw):
'''Register action. Called when used as an event 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 = AssetDelete(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()
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:]))

View file

@ -0,0 +1,103 @@
import sys
import argparse
import logging
import os
import getpass
import ftrack_api
from ftrack_action_handler import BaseAction
class ClientReviewSort(BaseAction):
'''Custom action.'''
#: Action identifier.
identifier = 'client.review.sort'
#: Action label.
label = 'Sort Review'
def discover(self, session, entities, event):
''' Validation '''
if (len(entities) == 0 or entities[0].entity_type != 'ReviewSession'):
return False
return True
def launch(self, session, entities, event):
entity = entities[0]
# Get all objects from Review Session and all 'sort order' possibilities
obj_list = []
sort_order_list = []
for obj in entity['review_session_objects']:
obj_list.append(obj)
sort_order_list.append(obj['sort_order'])
# Sort criteria
obj_list = sorted(obj_list, key=lambda k: k['version'])
obj_list = sorted(obj_list, key=lambda k: k['asset_version']['task']['name'])
obj_list = sorted(obj_list, key=lambda k: k['name'])
# Set 'sort order' to sorted list, so they are sorted in Ftrack also
for i in range(len(obj_list)):
obj_list[i]['sort_order'] = sort_order_list[i]
session.commit()
return {
'success': True,
'message': 'Client Review sorted!'
}
def register(session, **kw):
'''Register action. Called when used as an event plugin.'''
if not isinstance(session, ftrack_api.session.Session):
return
action_handler = ClientReviewSort(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()
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:]))

View file

@ -0,0 +1,122 @@
# :coding: utf-8
# :copyright: Copyright (c) 2015 Milan Kolar
import sys
import argparse
import logging
import getpass
import subprocess
import os
import ftrack_api
from ftrack_action_handler import BaseAction
class ComponentOpen(BaseAction):
'''Custom action.'''
# Action identifier
identifier = 'component.open'
# Action label
label = 'Open File'
# Action icon
icon = 'https://cdn4.iconfinder.com/data/icons/rcons-application/32/application_go_run-256.png',
def discover(self, session, entities, event):
''' Validation '''
if len(entities) != 1 or entities[0].entity_type != 'FileComponent':
return False
return True
def launch(self, session, entities, event):
entity = entities[0]
# Return error if component is on ftrack server
if entity['component_locations'][0]['location']['name'] == 'ftrack.server':
return {
'success': False,
'message': "This component is stored on ftrack server!"
}
# Get component filepath
# TODO with locations it will be different???
fpath = entity['component_locations'][0]['resource_identifier']
items = fpath.split(os.sep)
items.pop(-1)
fpath = os.sep.join(items)
if os.path.isdir(fpath):
if 'win' in sys.platform: # windows
subprocess.Popen('explorer "%s"' % fpath)
elif sys.platform == 'darwin': # macOS
subprocess.Popen(['open', fpath])
else: # linux
try:
subprocess.Popen(['xdg-open', fpath])
except OSError:
raise OSError('unsupported xdg-open call??')
else:
return {
'success': False,
'message': "Didn't found file: " + fpath
}
return {
'success': True,
'message': 'Component folder Opened'
}
def register(session, **kw):
'''Register action. Called when used as an event 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 = ComponentOpen(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()
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:]))

View file

@ -11,6 +11,7 @@ from ftrack_action_handler import BaseAction
from avalon import io, inventory, lib
from avalon.vendor import toml
class AvalonIdAttribute(BaseAction):
'''Edit meta data action.'''
@ -21,139 +22,21 @@ class AvalonIdAttribute(BaseAction):
#: 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.'''
''' Validation '''
self.logger.info('Got selection: {0}'.format(entities))
return self.validate_selection(session, entities)
# userId = event['source']['user']['id']
# user = session.query('User where id is ' + userId).one()
# if user['user_security_roles'][0]['security_role']['name'] != 'Administrator':
# return False
return True
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 -> <Create project> ELSE <Update Config>
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()
@ -161,44 +44,90 @@ class AvalonIdAttribute(BaseAction):
'user': user,
'status': 'running',
'data': json.dumps({
'description': 'Synch Ftrack to Avalon.'
'description': 'Custom Attribute creation.'
})
})
session.commit()
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 = []
# Attribute Name and Label
custAttrName = 'avalon_mongo_id'
custAttrLabel = 'Avalon/Mongo Id'
# Types that don't need object_type_id
base = {'show','asset','assetversion'}
# Don't create custom attribute on these entity types:
exceptions = ['task','milestone','library']
exceptions.extend(base)
# Get all possible object types
all_obj_types = session.query('ObjectType').all()
count_types = len(all_obj_types)
# Filter object types by exceptions
for index in range(count_types):
i = count_types - 1 - index
name = all_obj_types[i]['name'].lower()
def getShotAsset(entity):
if not (entity.entity_type in ['Task']):
if entity not in importable:
importable.append(entity)
if " " in name:
name = name.replace(" ","")
if entity['children']:
childrens = entity['children']
for child in childrens:
getShotAsset(child)
if name in exceptions:
all_obj_types.pop(i)
# get all entities separately
for entity in entities:
entity_type, entity_id = entity
act_ent = session.get(entity_type, entity_id)
getShotAsset(act_ent)
# Get IDs of filtered object types
all_obj_types_id = set()
for obj in all_obj_types:
all_obj_types_id.add(obj['id'])
for e in importable:
self.importToAvalon(session, e)
# Get all custom attributes
current_cust_attr = session.query('CustomAttributeConfiguration').all()
# Filter already existing AvalonMongoID attr.
for attr in current_cust_attr:
if attr['key'] == custAttrName:
if attr['entity_type'] in base:
base.remove(attr['entity_type'])
if attr['object_type_id'] in all_obj_types_id:
all_obj_types_id.remove(attr['object_type_id'])
# Set session back to begin("session.query" raises error on commit)
session.rollback()
# Set security roles for attribute
custAttrSecuRole = session.query('SecurityRole').all()
# Set Text type of Attribute
custom_attribute_type = session.query(
'CustomAttributeType where name is "text"'
).one()
for entity_type in base:
# Create a custom attribute configuration.
session.create('CustomAttributeConfiguration', {
'entity_type': entity_type,
'type': custom_attribute_type,
'label': custAttrLabel,
'key': custAttrName,
'default': '',
'write_security_roles': custAttrSecuRole,
'read_security_roles': custAttrSecuRole,
'config': json.dumps({'markdown': False})
})
for type in all_obj_types_id:
# Create a custom attribute configuration.
session.create('CustomAttributeConfiguration', {
'entity_type': 'task',
'object_type_id': type,
'type': custom_attribute_type,
'label': custAttrLabel,
'key': custAttrName,
'default': '',
'write_security_roles': custAttrSecuRole,
'read_security_roles': custAttrSecuRole,
'config': json.dumps({'markdown': False})
})
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("Creating custom attributes failed")
print(e)
return True
@ -215,7 +144,6 @@ def register(session, **kw):
action_handler = AvalonIdAttribute(session)
action_handler.register()
print("----- action - <" + action_handler.__class__.__name__ + "> - Has been registered -----")
def main(arguments=None):

View file

@ -0,0 +1,96 @@
import sys
import argparse
import logging
import getpass
import ftrack_api
from ftrack_action_handler import BaseAction
class VersionsCleanup(BaseAction):
'''Custom action.'''
# Action identifier
identifier = 'versions.cleanup'
# Action label
label = 'Versions cleanup'
def discover(self, session, entities, event):
''' Validation '''
# Only 1 AssetVersion is allowed
if len(entities) != 1 or entities[0].entity_type != 'AssetVersion':
return False
return True
def launch(self, session, entities, event):
entity = entities[0]
# Go through all versions in asset
for version in entity['asset']['versions']:
if not version['is_published']:
session.delete(version)
try:
session.commit()
except:
session.rollback()
raise
return {
'success': True,
'message': 'removed hidden versions'
}
def register(session, **kw):
'''Register action. Called when used as an event 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 = VersionsCleanup(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()
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:]))

View file

@ -20,20 +20,14 @@ class JobKiller(BaseAction):
description = 'Killing all running jobs younger than day'
def validate_selection(self, session, entities):
'''Return if *entities* is a valid selection.'''
pass
def discover(self, session, entities, event):
''' Validation '''
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):
""" JOB SETTING """
""" GET JOB """
yesterday = datetime.date.today() - datetime.timedelta(days=1)
@ -48,7 +42,10 @@ class JobKiller(BaseAction):
print('Changing Job ({}) status: {} -> failed'.format(job['id'], job['status']))
job['status'] = 'failed'
session.commit()
try:
session.commit()
except:
session.rollback()
print('All running jobs were killed Successfully!')
return {

View file

@ -0,0 +1,125 @@
import sys
import argparse
import logging
import getpass
import ftrack_api
from ftrack_action_handler import BaseAction
class SetVersion(BaseAction):
'''Custom action.'''
#: Action identifier.
identifier = 'version.set'
#: Action label.
label = 'Version Set'
def discover(self, session, entities, event):
''' Validation '''
# Only 1 AssetVersion is allowed
if len(entities) != 1 or entities[0].entity_type != 'AssetVersion':
return False
return True
def interface(self, session, entities, event):
if not event['data'].get('values', {}):
entity = entities[0]
# Get actual version of asset
act_ver = entity['version']
# Set form
items = [{
'label': 'Version number',
'type': 'number',
'name': 'version_number',
'value': act_ver
}]
return items
def launch(self, session, entities, event):
entity = entities[0]
# Do something with the values or return a new form.
values = event['data'].get('values', {})
# Default is action True
scs = True
msg = 'Version was changed to v{0}'.format(values['version_number'])
if not values['version_number']:
scs = False,
msg = "You didn't enter any version."
elif int(values['version_number']) <= 0:
scs = False
msg = 'Negative or zero version is not valid.'
else:
entity['version'] = values['version_number']
try:
session.commit()
except:
session.rollback()
raise
return {
'success': scs,
'message': msg
}
def register(session, **kw):
'''Register action. Called when used as an event 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 = SetVersion(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()
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:]))

View file

@ -4,8 +4,8 @@ import sys
import argparse
import logging
import os
import json
import ftrack_api
import json
from ftrack_action_handler import BaseAction
from avalon import io, inventory, lib
@ -23,27 +23,11 @@ class SyncToAvalon(BaseAction):
#: 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.'''
''' Validation '''
self.logger.info('Got selection: {0}'.format(entities))
return self.validate_selection(session, entities)
return True
def importToAvalon(self, session, entity):
@ -51,6 +35,7 @@ class SyncToAvalon(BaseAction):
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:
@ -92,6 +77,7 @@ class SyncToAvalon(BaseAction):
# --- Create project and assets in Avalon ---
io.install()
## ----- PROJECT ------
# If project don't exists -> <Create project> ELSE <Update Config>
if (io.find_one({'type': 'project',
'name': entityProj['full_name']}) is None):
@ -112,10 +98,12 @@ class SyncToAvalon(BaseAction):
# If entity is Project or have only 1 entity kill action
if (len(eLinks) > 1) and not (eLinks[-1]['type'] in ['Project']):
## ----- ASSETS ------
# Presets:
# 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
# Get list of assets without project
assets = []
for i in range(1, len(eLinks)):
assets.append(eLinks[i])
@ -137,23 +125,32 @@ class SyncToAvalon(BaseAction):
tasks.append(child['name'])
data.update({'tasks': tasks})
if (io.find_one({'type': 'asset', 'name': asset['name']}) is None):
# Create asset in DB
# Try to find asset in current database
avalon_asset = io.find_one({'type': 'asset', 'name': asset['name']})
# Create if don't exists
if avalon_asset is None:
inventory.create_asset(asset['name'], silo, data, projectId)
print("Asset "+asset['name']+" - created")
# Raise error if it seems to be different ent. with same name
elif (avalon_asset['data']['ftrackId'] != data['ftrackId'] or
avalon_asset['data']['visualParent'] != data['visualParent'] or
avalon_asset['data']['parents'] != data['parents']):
raise ValueError('Possibility of entity name duplication: {}'.format(asset['name']))
# Else update info
else:
io.update_many({'type': 'asset','name': asset['name']},
{'$set':{'data':data}})
{'$set':{'data':data, 'silo': silo}})
# TODO check if is asset in same folder!!! ???? FEATURE FOR FUTURE
print("Asset "+asset["name"]+" - already exist")
print("Asset "+asset["name"]+" - updated")
# Get parent ID and store it to data
parentId = io.find_one({'type': 'asset', 'name': asset['name']})['_id']
hierarchy = os.path.sep.join(folderStruct)
data.update({'visualParent': parentId, 'parents': folderStruct,
'hierarchy': hierarchy})
folderStruct.append(asset['name'])
## 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 custAttrName in entity['custom_attributes'] and entity['custom_attributes'][custAttrName] is '':
entity['custom_attributes'][custAttrName] = str(parentId)
@ -161,6 +158,8 @@ class SyncToAvalon(BaseAction):
io.uninstall()
def launch(self, session, entities, event):
message = ""
# JOB SETTINGS
userId = event['source']['user']['id']
user = session.query('User where id is ' + userId).one()
@ -175,10 +174,8 @@ 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"
os.environ['AVALON_SILO'] = "tmp"
#TODO AVALON_PROJECTS, AVALON_ASSET, AVALON_SILO should be set up otherwise console log shows avalon debug
importable = []
def getShotAsset(entity):
@ -191,25 +188,33 @@ class SyncToAvalon(BaseAction):
for child in childrens:
getShotAsset(child)
# get all entities separately
# get all entities separately/unique
for entity in entities:
entity_type, entity_id = entity
act_ent = session.get(entity_type, entity_id)
getShotAsset(act_ent)
getShotAsset(entity)
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)
message = str(e)
return True
if len(message) > 0:
return {
'success': False,
'message': message
}
return {
'success': True,
'message': "Synchronization was successfull"
}
def register(session, **kw):
@ -223,7 +228,6 @@ def register(session, **kw):
action_handler = SyncToAvalon(session)
action_handler.register()
print("----- action - <" + action_handler.__class__.__name__ + "> - Has been registered -----")
def main(arguments=None):

View file

@ -18,26 +18,38 @@ class TestAction(BaseAction):
#: 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.'''
''' Validation '''
return True
self.logger.info('Got selection: {0}'.format(entities))
return self.validate_selection(session, entities)
def launch(self, session, entities, event):
for entity in entities:
index = 0
name = entity['components'][index]['name']
filetype = entity['components'][index]['file_type']
path = entity['components'][index]['component_locations'][0]['resource_identifier']
# entity['components'][index]['component_locations'][0]['resource_identifier'] = r"C:\Users\jakub.trllo\Desktop\test\exr\int_c022_lighting_v001_main_AO.%04d.exr"
location = entity['components'][0]['component_locations'][0]['location']
component = entity['components'][0]
# print(location.get_filesystem_path(component))
# for k in p:
# print(100*"-")
# print(k)
# print(p[k])
return True

View file

@ -0,0 +1,114 @@
# :coding: utf-8
# :copyright: Copyright (c) 2015 Milan Kolar
import sys
import argparse
import logging
import getpass
import json
import ftrack_api
from ftrack_action_handler import BaseAction
class ThumbToChildren(BaseAction):
'''Custom action.'''
# Action identifier
identifier = 'thumb.to.children'
# Action label
label = 'Thumbnail to Children'
# Action icon
icon = "https://cdn3.iconfinder.com/data/icons/transfers/100/239322-download_transfer-128.png"
def discover(self, session, entities, event):
''' Validation '''
if (len(entities) != 1 or entities[0].entity_type in ['Project']):
return False
return True
def launch(self, session, entities, event):
'''Callback method for action.'''
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': 'Push thumbnails to Childrens'
})
})
try:
for entity in entities:
thumbid = entity['thumbnail_id']
if thumbid:
for child in entity['children']:
child['thumbnail_id'] = thumbid
# inform the user that the job is done
job['status'] = 'done'
except:
# fail the job if something goes wrong
job['status'] = 'failed'
raise
finally:
session.commit()
return {
'success': True,
'message': 'Created job for updating thumbnails!'
}
def register(session, **kw):
'''Register action. Called when used as an event plugin.'''
if not isinstance(session, ftrack_api.session.Session):
return
action_handler = ThumbToChildren(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()
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:]))

View file

@ -0,0 +1,130 @@
# :coding: utf-8
# :copyright: Copyright (c) 2015 Milan Kolar
import sys
import argparse
import logging
import getpass
import json
import ftrack_api
from ftrack_action_handler import BaseAction
class ThumbToParent(BaseAction):
'''Custom action.'''
# Action identifier
identifier = 'thumb.to.parent'
# Action label
label = 'Thumbnail to Parent'
# Action icon
icon = "https://cdn3.iconfinder.com/data/icons/transfers/100/239419-upload_transfer-512.png"
def discover(self, session, entities, event):
'''Return action config if triggered on asset versions.'''
if len(entities) <= 0 or entities[0].entity_type in ['Project']:
return False
return True
def launch(self, session, entities, event):
'''Callback method for action.'''
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': 'Push thumbnails to parents'
})
})
try:
for entity in entities:
parent = None
thumbid = None
if entity.entity_type.lower() == 'assetversion':
try:
parent = entity['task']
except:
par_ent = entity['link'][-2]
parent = session.get(par_ent['type'], par_ent['id'])
else:
try:
parent = entity['parent']
except:
print("Durin Action 'Thumb to Parent' went something wrong")
thumbid = entity['thumbnail_id']
if parent and thumbid:
parent['thumbnail_id'] = thumbid
status = 'done'
else:
status = 'failed'
# inform the user that the job is done
job['status'] = status or 'done'
except:
# fail the job if something goes wrong
job['status'] = 'failed'
raise
finally:
session.commit()
return {
'success': True,
'message': 'Created job for updating thumbnails!'
}
def register(session, **kw):
'''Register action. Called when used as an event plugin.'''
if not isinstance(session, ftrack_api.session.Session):
return
action_handler = ThumbToParent(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()
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:]))

View file

@ -0,0 +1,403 @@
import logging
import subprocess
import sys
import os
import re
from operator import itemgetter
import ftrack_api
class DJVViewAction(object):
"""Launch DJVView action."""
identifier = "djvview-launch-action"
# label = "DJV View"
# icon = "http://a.fsdn.com/allura/p/djv/icon"
def __init__(self, session):
'''Expects a ftrack_api.Session instance'''
self.logger = logging.getLogger(
'{0}.{1}'.format(__name__, self.__class__.__name__)
)
if self.identifier is None:
raise ValueError(
'Action missing identifier.'
)
self.session = session
def is_valid_selection(self, event):
selection = event["data"].get("selection", [])
if not selection:
return
entityType = selection[0]["entityType"]
if entityType not in ["assetversion", "task"]:
return False
return True
def discover(self, event):
"""Return available actions based on *event*. """
if not self.is_valid_selection(event):
return
items = []
applications = self.get_applications()
applications = sorted(
applications, key=lambda application: application["label"]
)
for application in applications:
self.djv_path = application.get("path", None)
applicationIdentifier = application["identifier"]
label = application["label"]
items.append({
"actionIdentifier": self.identifier,
"label": label,
"variant": application.get("variant", None),
"description": application.get("description", None),
"icon": application.get("icon", "default"),
"applicationIdentifier": applicationIdentifier
})
return {
"items": items
}
def register(self):
'''Registers the action, subscribing the discover and launch topics.'''
self.session.event_hub.subscribe(
'topic=ftrack.action.discover and source.user.username={0}'.format(
self.session.api_user
), self.discover
)
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
)
print("----- action - <" + self.__class__.__name__ + "> - Has been registered -----")
def get_applications(self):
applications = []
label = "DJVView {version}"
versionExpression = re.compile(r"(?P<version>\d+.\d+.\d+)")
applicationIdentifier = "djvview"
description = "DJV View Launcher"
icon = "http://a.fsdn.com/allura/p/djv/icon"
expression = []
if sys.platform == "win32":
expression = ["C:\\", "Program Files", "djv-\d.+",
"bin", "djv_view.exe"]
elif sys.platform == "darwin":
expression = ["Application", "DJV.app", "Contents", "MacOS", "DJV"]
# Linuxs
else:
expression = ["usr", "local", "djv", "djv_view"]
pieces = expression[:]
start = pieces.pop(0)
if sys.platform == 'win32':
# On Windows C: means current directory so convert roots that look
# like drive letters to the C:\ format.
if start and start[-1] == ':':
start += '\\'
if not os.path.exists(start):
raise ValueError(
'First part "{0}" of expression "{1}" must match exactly to an '
'existing entry on the filesystem.'
.format(start, expression)
)
expressions = list(map(re.compile, pieces))
expressionsCount = len(expression)-1
for location, folders, files in os.walk(start, topdown=True, followlinks=True):
level = location.rstrip(os.path.sep).count(os.path.sep)
expression = expressions[level]
if level < (expressionsCount - 1):
# If not yet at final piece then just prune directories.
folders[:] = [folder for folder in folders
if expression.match(folder)]
else:
# Match executable. Note that on OSX executable might equate to
# a folder (.app).
for entry in folders + files:
match = expression.match(entry)
if match:
# Extract version from full matching path.
path = os.path.join(start, location, entry)
versionMatch = versionExpression.search(path)
if versionMatch:
version = versionMatch.group('version')
applications.append({
'identifier': applicationIdentifier.format(
version=version
),
'path': path,
'version': version,
'label': label.format(version=version),
'icon': icon,
# 'variant': variant.format(version=version),
'description': description
})
else:
self.logger.debug(
'Discovered application executable, but it '
'does not appear to o contain required version '
'information: {0}'.format(path)
)
# Don't descend any further as out of patterns to match.
del folders[:]
return applications
def translate_event(self, session, event):
'''Return *event* translated structure to be used with the API.'''
selection = event['data'].get('selection', [])
entities = list()
for entity in selection:
entities.append(
(session.get(self.get_entity_type(entity), entity.get('entityId')))
)
return entities
def get_entity_type(self, entity):
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):
"""Callback method for DJVView action."""
session = self.session
entities = self.translate_event(session, event)
# Launching application
if "values" in event["data"]:
filename = event['data']['values']['path']
file_type = filename.split(".")[-1]
# TODO Is this proper way?
try:
fps = int(entities[0]['custom_attributes']['fps'])
except:
fps = 24
# TODO issequence is probably already built-in validation in ftrack
isseq = re.findall('%[0-9]*d', filename)
if len(isseq) > 0:
if len(isseq) == 1:
frames = []
padding = re.findall('%[0-9]*d', filename).pop()
index = filename.find(padding)
full_file = filename[0:index-1]
file = full_file.split(os.sep)[-1]
folder = os.path.dirname(full_file)
for fname in os.listdir(path=folder):
if fname.endswith(file_type) and file in fname:
frames.append(int(fname.split(".")[-2]))
if len(frames) > 0:
start = min(frames)
end = max(frames)
range = (padding % start) + '-' + (padding % end)
filename = re.sub('%[0-9]*d', range, filename)
else:
print("")
return {
'success': False,
'message': 'DJV View - Filename has more than one seqence identifier.'
}
cmd = []
# DJV path
cmd.append(os.path.normpath(self.djv_path))
# DJV Options Start ##############################################
# cmd.append('-file_layer (value)') #layer name
cmd.append('-file_proxy 1/2') # Proxy scale: 1/2, 1/4, 1/8
cmd.append('-file_cache True') # Cache: True, False.
# cmd.append('-window_fullscreen') #Start in full screen
# cmd.append("-window_toolbar False") # Toolbar controls: False, True.
# cmd.append("-window_playbar False") # Window controls: False, True.
# cmd.append("-view_grid None") # Grid overlay: None, 1x1, 10x10, 100x100.
# cmd.append("-view_hud True") # Heads up display: True, False.
cmd.append("-playback Forward") # Playback: Stop, Forward, Reverse.
# cmd.append("-playback_frame (value)") # Frame.
cmd.append("-playback_speed " + str(fps))
# cmd.append("-playback_timer (value)") # Timer: Sleep, Timeout. Value: Sleep.
# cmd.append("-playback_timer_resolution (value)") # Timer resolution (seconds): 0.001.
cmd.append("-time_units Frames") # Time units: Timecode, Frames.
# DJV Options End ################################################
# PATH TO COMPONENT
cmd.append(os.path.normpath(filename))
# Run DJV with these commands
subprocess.Popen(' '.join(cmd))
return {
'success': True,
'message': 'DJV View started.'
}
if 'items' not in event["data"]:
event["data"]['items'] = []
try:
for entity in entities:
versions = []
allowed_types = ["img", "mov", "exr"]
if entity.entity_type.lower() == "assetversion":
if entity['components'][0]['file_type'] in allowed_types:
versions.append(entity)
if entity.entity_type.lower() == "task":
# AssetVersions are obtainable only from shot!
shotentity = entity['parent']
for asset in shotentity['assets']:
for version in asset['versions']:
# Get only AssetVersion of selected task
if version['task']['id'] != entity['id']:
continue
# Get only components with allowed type
if version['components'][0]['file_type'] in allowed_types:
versions.append(version)
# Raise error if no components were found
if len(versions) < 1:
raise ValueError('There are no Asset Versions to open.')
for version in versions:
for component in version['components']:
label = "v{0} - {1} - {2}"
label = label.format(
str(version['version']).zfill(3),
version['asset']['type']['name'],
component['name']
)
try:
# TODO This is proper way to get filepath!!!
# THIS WON'T WORK RIGHT NOW
location = component['component_locations'][0]['location']
file_path = location.get_filesystem_path(component)
# if component.isSequence():
# if component.getMembers():
# frame = int(component.getMembers()[0].getName())
# file_path = file_path % frame
except:
# This works but is NOT proper way
file_path = component['component_locations'][0]['resource_identifier']
event["data"]["items"].append(
{"label": label, "value": file_path}
)
except Exception as e:
return {
'success': False,
'message': str(e)
}
return {
"items": [
{
"label": "Items to view",
"type": "enumerator",
"name": "path",
"data": sorted(
event["data"]['items'],
key=itemgetter("label"),
reverse=True
)
}
]
}
def register(session, **kw):
"""Register hooks."""
if not isinstance(session, ftrack_api.session.Session):
return
action = DJVViewAction(session)
action.register()
def main(arguments=None):
'''Set up logging and register action.'''
if arguments is None:
arguments = []
parser = argparse.ArgumentParser()
# Allow setting of logging level from arguments.
loggingLevels = {}
for level in (
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
logging.ERROR, logging.CRITICAL
):
loggingLevels[logging.getLevelName(level).lower()] = level
parser.add_argument(
'-v', '--verbosity',
help='Set the logging output verbosity.',
choices=loggingLevels.keys(),
default='info'
)
namespace = parser.parse_args(arguments)
# Set up basic logging
logging.basicConfig(level=loggingLevels[namespace.verbosity])
session = ftrack_api.Session()
register(session)
# Wait for events
logging.info(
'Registered actions and listening for events. Use Ctrl-C to abort.'
)
session.event_hub.wait()
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -0,0 +1,428 @@
import os
import operator
import ftrack_api
import collections
import sys
import json
import base64
# sys.path.append(os.path.dirname(os.path.dirname(__file__)))
# from ftrack_kredenc.lucidity.vendor import yaml
# from ftrack_kredenc import lucidity
#
#
# def get_ftrack_connect_path():
#
# ftrack_connect_root = os.path.abspath(os.getenv('FTRACK_CONNECT_PACKAGE'))
#
# return ftrack_connect_root
#
#
# def from_yaml(filepath):
# ''' Parse a Schema from a YAML file at the given *filepath*.
# '''
# with open(filepath, 'r') as f:
# data = yaml.safe_load(f)
# return data
#
#
# def get_task_enviro(entity, environment=None):
#
# context = get_context(entity)
#
# if not environment:
# environment = {}
#
# for key in context:
# os.environ[key.upper()] = context[key]['name']
# environment[key.upper()] = context[key]['name']
#
# if key == 'Project':
# os.putenv('PROJECT_ROOT', context[key]['root'])
# os.environ['PROJECT_ROOT'] = context[key]['root']
# environment['PROJECT_ROOT'] = context[key]['root']
# print('PROJECT_ROOT: ' + context[key]['root'])
# print(key + ': ' + context[key]['name'])
#
# return environment
#
#
# def get_entity():
# decodedEventData = json.loads(
# base64.b64decode(
# os.environ.get('FTRACK_CONNECT_EVENT')
# )
# )
#
# entity = decodedEventData.get('selection')[0]
#
# if entity['entityType'] == 'task':
# return ftrack_api.Task(entity['entityId'])
# else:
# return None
#
#
# def set_env_vars():
#
# entity = get_entity()
#
# if entity:
# if not os.environ.get('project_root'):
# enviro = get_task_enviro(entity)
#
# print(enviro)
#
#
def get_context(entity):
entityName = entity['name']
entityId = entity['id']
entityType = entity.entity_type
entityDescription = entity['description']
print(100*"*")
for k in entity['ancestors']:
print(k['name'])
print(100*"*")
hierarchy = entity.getParents()
ctx = collections.OrderedDict()
if entity.get('entityType') == 'task' and entityType == 'Task':
taskType = entity.getType().getName()
entityDic = {
'type': taskType,
'name': entityName,
'id': entityId,
'description': entityDescription
}
elif entity.get('entityType') == 'task':
entityDic = {
'name': entityName,
'id': entityId,
'description': entityDescription
}
ctx[entityType] = entityDic
folder_counter = 0
for ancestor in hierarchy:
tempdic = {}
if isinstance(ancestor, ftrack_api.Component):
# Ignore intermediate components.
continue
tempdic['name'] = ancestor.getName()
tempdic['id'] = ancestor.getId()
try:
objectType = ancestor.getObjectType()
tempdic['description'] = ancestor.getDescription()
except AttributeError:
objectType = 'Project'
tempdic['description'] = ''
if objectType == 'Asset Build':
tempdic['type'] = ancestor.getType().get('name')
objectType = objectType.replace(' ', '_')
elif objectType == 'Project':
tempdic['code'] = tempdic['name']
tempdic['name'] = ancestor.get('fullname')
tempdic['root'] = ancestor.getRoot()
if objectType == 'Folder':
objectType = objectType + str(folder_counter)
folder_counter += 1
ctx[objectType] = tempdic
return ctx
def getNewContext(entity):
parents = []
item = entity
while True:
item = item['parent']
if not item:
break
parents.append(item)
ctx = collections.OrderedDict()
entityDic = {
'name': entity['name'],
'id': entity['id'],
}
try:
entityDic['type'] = entity['type']['name']
except:
pass
ctx[entity['object_type']['name']] = entityDic
print(100*"-")
for p in parents:
print(p)
# add all parents to the context
for parent in parents:
tempdic = {}
if not parent.get('project_schema'):
tempdic = {
'name': parent['full_name'],
'code': parent['name'],
'id': parent['id'],
}
tempdic = {
'name': parent['name'],
'id': parent['id'],
}
object_type = parent['object_type']['name']
ctx[object_type] = tempdic
# add project to the context
project = entity['project']
ctx['Project'] = {
'name': project['full_name'],
'code': project['name'],
'id': project['id'],
'root': project['root'],
},
return ctx
#
#
# def get_frame_range():
#
# entity = get_entity()
# entityType = entity.getObjectType()
# environment = {}
#
# if entityType == 'Task':
# try:
# environment['FS'] = str(int(entity.getFrameStart()))
# except Exception:
# environment['FS'] = '1'
# try:
# environment['FE'] = str(int(entity.getFrameEnd()))
# except Exception:
# environment['FE'] = '1'
# else:
# try:
# environment['FS'] = str(int(entity.getFrameStart()))
# except Exception:
# environment['FS'] = '1'
# try:
# environment['FE'] = str(int(entity.getFrameEnd()))
# except Exception:
# environment['FE'] = '1'
#
#
# def get_asset_name_by_id(id):
# for t in ftrack_api.getAssetTypes():
# try:
# if t.get('typeid') == id:
# return t.get('name')
# except:
# return None
#
#
# def get_status_by_name(name):
# statuses = ftrack_api.getTaskStatuses()
#
# result = None
# for s in statuses:
# if s.get('name').lower() == name.lower():
# result = s
#
# return result
#
#
# def sort_types(types):
# data = {}
# for t in types:
# data[t] = t.get('sort')
#
# data = sorted(data.items(), key=operator.itemgetter(1))
# results = []
# for item in data:
# results.append(item[0])
#
# return results
#
#
# def get_next_task(task):
# shot = task.getParent()
# tasks = shot.getTasks()
#
# types_sorted = sort_types(ftrack_api.getTaskTypes())
#
# next_types = None
# for t in types_sorted:
# if t.get('typeid') == task.get('typeid'):
# try:
# next_types = types_sorted[(types_sorted.index(t) + 1):]
# except:
# pass
#
# for nt in next_types:
# for t in tasks:
# if nt.get('typeid') == t.get('typeid'):
# return t
#
# return None
#
#
# def get_latest_version(versions):
# latestVersion = None
# if len(versions) > 0:
# versionNumber = 0
# for item in versions:
# if item.get('version') > versionNumber:
# versionNumber = item.getVersion()
# latestVersion = item
# return latestVersion
#
#
# def get_thumbnail_recursive(task):
# if task.get('thumbid'):
# thumbid = task.get('thumbid')
# return ftrack_api.Attachment(id=thumbid)
# if not task.get('thumbid'):
# parent = ftrack_api.Task(id=task.get('parent_id'))
# return get_thumbnail_recursive(parent)
#
#
# # paths_collected
#
# def getFolderHierarchy(context):
# '''Return structure for *hierarchy*.
# '''
#
# hierarchy = []
# for key in reversed(context):
# hierarchy.append(context[key]['name'])
# print(hierarchy)
#
# return os.path.join(*hierarchy[1:-1])
#
#
def tweakContext(context, include=False):
for key in context:
if key == 'Asset Build':
context['Asset_Build'] = context.pop(key)
key = 'Asset_Build'
description = context[key].get('description')
if description:
context[key]['description'] = '_' + description
hierarchy = []
for key in reversed(context):
hierarchy.append(context[key]['name'])
if include:
hierarchy = os.path.join(*hierarchy[1:])
else:
hierarchy = os.path.join(*hierarchy[1:-1])
context['ft_hierarchy'] = hierarchy
def getSchema(entity):
project = entity['project']
schema = project['project_schema']['name']
tools = os.path.abspath(os.environ.get('studio_tools'))
schema_path = os.path.join(tools, 'studio', 'templates', (schema + '_' + project['name'] + '.yml'))
if not os.path.exists(schema_path):
schema_path = os.path.join(tools, 'studio', 'templates', (schema + '.yml'))
if not os.path.exists(schema_path):
schema_path = os.path.join(tools, 'studio', 'templates', 'default.yml')
schema = lucidity.Schema.from_yaml(schema_path)
print(schema_path)
return schema
# def getAllPathsYaml(entity, root=''):
#
# if isinstance(entity, str) or isinstance(entity, unicode):
# entity = ftrack_api.Task(entity)
#
# context = get_context(entity)
#
# tweakContext(context)
#
# schema = getSchema(entity)
#
# paths = schema.format_all(context)
# paths_collected = []
#
# for path in paths:
# tweak_path = path[0].replace(" ", '_').replace('\'', '').replace('\\', '/')
#
# tempPath = os.path.join(root, tweak_path)
# path = list(path)
# path[0] = tempPath
# paths_collected.append(path)
#
# return paths_collected
#
def getPathsYaml(entity, templateList=None, root=None, **kwargs):
'''
version=None
ext=None
item=None
family=None
subset=None
'''
context = get_context(entity)
if entity.entity_type != 'Task':
tweakContext(context, include=True)
else:
tweakContext(context)
context.update(kwargs)
host = sys.executable.lower()
ext = None
if not context.get('ext'):
if "nuke" in host:
ext = 'nk'
elif "maya" in host:
ext = 'ma'
elif "houdini" in host:
ext = 'hip'
if ext:
context['ext'] = ext
if not context.get('subset'):
context['subset'] = ''
else:
context['subset'] = '_' + context['subset']
schema = getSchema(entity)
paths = schema.format_all(context)
paths_collected = set([])
for temp_mask in templateList:
for path in paths:
if temp_mask in path[1].name:
path = path[0].lower().replace(" ", '_').replace('\'', '').replace('\\', '/')
path_list = path.split('/')
if path_list[0].endswith(':'):
path_list[0] = path_list[0] + os.path.sep
path = os.path.join(*path_list)
temppath = os.path.join(root, path)
paths_collected.add(temppath)
return list(paths_collected)

View file

@ -19,7 +19,6 @@ t = Templates(
)
class AppAction(object):
'''Custom Action base class
@ -53,14 +52,13 @@ class AppAction(object):
self.icon = icon
self.description = description
@property
def session(self):
'''Return current session.'''
return self._session
def register(self):
'''Registers the action, subscribing the the discover and launch topics.'''
'''Registers the action, subscribing the discover and launch topics.'''
self.session.event_hub.subscribe(
'topic=ftrack.action.discover and source.user.username={0}'.format(
self.session.api_user
@ -85,6 +83,7 @@ class AppAction(object):
)
if accepts:
self.logger.info('Selection is valid')
return {
'items': [{
'label': self.label,
@ -94,17 +93,18 @@ class AppAction(object):
'icon': self.icon,
}]
}
else:
self.logger.info('Selection is _not_ valid')
def discover(self, session, entities, event):
'''Return true if we can handle the selected entities.
*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
*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
@ -221,6 +221,7 @@ class AppAction(object):
*event* the unmodified original event
'''
# TODO Delete this line
print("Action - {0} ({1}) - just started".format(self.label, self.identifier))
@ -228,8 +229,8 @@ class AppAction(object):
entity = session.get(entity, id)
silo = "Film"
if entity.entity_type=="AssetBuild":
silo= "Asset"
if entity.entity_type == "AssetBuild":
silo = "Asset"
# set environments for Avalon
os.environ["AVALON_PROJECT"] = entity['project']['full_name']
@ -239,24 +240,22 @@ class AppAction(object):
os.environ["AVALON_APP"] = self.identifier
os.environ["AVALON_APP_NAME"] = self.identifier + "_" + self.variant
anatomy = t.anatomy
io.install()
hierarchy = io.find_one({"type":'asset', "name":entity['parent']['name']})['data']['parents']
hierarchy = io.find_one({"type": 'asset', "name": entity['parent']['name']})['data']['parents']
io.uninstall()
if hierarchy:
# hierarchy = os.path.sep.join(hierarchy)
hierarchy = os.path.join(*hierarchy)
data = { "project": {"name": entity['project']['full_name'],
data = {"project": {"name": entity['project']['full_name'],
"code": entity['project']['name']},
"task": entity['name'],
"asset": entity['parent']['name'],
"hierarchy": hierarchy}
"task": entity['name'],
"asset": entity['parent']['name'],
"hierarchy": hierarchy}
anatomy = anatomy.format(data)
os.environ["AVALON_WORKDIR"] = os.path.join(anatomy.work.root, anatomy.work.folder)
# TODO Add paths to avalon setup from tomls
@ -309,6 +308,13 @@ class AppAction(object):
'message': "We didn't found launcher for {0}".format(self.label)
}
# RUN TIMER IN FTRACK
username = event['source']['user']['username']
user = session.query('User where username is "{}"'.format(username)).one()
task = session.query('Task where id is {}'.format(entity['id'])).one()
print('Starting timer for task: ' + task['name'])
user.start_timer(task, force=True)
return {
'success': True,
'message': "Launching {0}".format(self.label)
@ -424,6 +430,7 @@ class BaseAction(object):
),
self._launch
)
print("----- action - <" + self.__class__.__name__ + "> - Has been registered -----")
def _discover(self, event):
args = self._translate_event(
@ -435,6 +442,7 @@ class BaseAction(object):
)
if accepts:
self.logger.info(u'Discovering action with selection: {0}'.format(args[1]['data'].get('selection', [])))
return {
'items': [{
'label': self.label,
@ -472,7 +480,8 @@ class BaseAction(object):
for entity in _selection:
_entities.append(
(
self._get_entity_type(entity), entity.get('entityId')
session.get(self._get_entity_type(entity), entity.get('entityId'))
# self._get_entity_type(entity), entity.get('entityId')
)
)