diff --git a/pype/ftrack/actions/action_create_folders.py b/pype/ftrack/actions/action_create_folders.py
index 7ce5526164..f7620cd609 100644
--- a/pype/ftrack/actions/action_create_folders.py
+++ b/pype/ftrack/actions/action_create_folders.py
@@ -1,13 +1,15 @@
-import logging
import os
-import argparse
import sys
-import errno
+import logging
+import argparse
+import re
+import json
import ftrack_api
from pype.ftrack import BaseAction
-import json
-from pype import api as pype
+from pype import api as pype, lib as pypelib
+from avalon import lib as avalonlib
+from avalon.tools.libraryloader.io_nonsingleton import DbConnector
class CreateFolders(BaseAction):
@@ -25,111 +27,312 @@ class CreateFolders(BaseAction):
'https://cdn1.iconfinder.com/data/icons/hawcons/32/'
'698620-icon-105-folder-add-512.png'
)
+ db = DbConnector()
def discover(self, session, entities, event):
''' Validation '''
-
+ not_allowed = ['assetversion']
+ if len(entities) != 1:
+ return False
+ if entities[0].entity_type.lower() in not_allowed:
+ return False
return True
- def getShotAsset(self, entity):
- if entity not in self.importable:
- if entity['object_type']['name'] != 'Task':
- self.importable.add(entity)
+ def interface(self, session, entities, event):
+ if event['data'].get('values', {}):
+ return
+ entity = entities[0]
+ without_interface = True
+ for child in entity['children']:
+ if child['object_type']['name'].lower() != 'task':
+ without_interface = False
+ break
+ self.without_interface = without_interface
+ if without_interface:
+ return
+ title = 'Create folders'
- if entity['children']:
- children = entity['children']
- for child in children:
- self.getShotAsset(child)
+ entity_name = entity['name']
+ msg = (
+ '
Do you want create folders also'
+ ' for all children of "{}"?
'
+ )
+ if entity.entity_type.lower() == 'project':
+ entity_name = entity['full_name']
+ msg = msg.replace(' also', '')
+ msg += '(Project root won\'t be created if not checked)
'
+ items = []
+ item_msg = {
+ 'type': 'label',
+ 'value': msg.format(entity_name)
+ }
+ item_label = {
+ 'type': 'label',
+ 'value': 'With all chilren entities'
+ }
+ item = {
+ 'name': 'children_included',
+ 'type': 'boolean',
+ 'value': False
+ }
+ items.append(item_msg)
+ items.append(item_label)
+ items.append(item)
+
+ if len(items) == 0:
+ return {
+ 'success': False,
+ 'message': 'Didn\'t found any running jobs'
+ }
+ else:
+ return {
+ 'items': items,
+ 'title': title
+ }
def launch(self, session, entities, event):
'''Callback method for custom action.'''
+ with_childrens = True
+ if self.without_interface is False:
+ if 'values' not in event['data']:
+ return
+ with_childrens = event['data']['values']['children_included']
+ entity = entities[0]
+ if entity.entity_type.lower() == 'project':
+ proj = entity
+ else:
+ proj = entity['project']
+ project_name = proj['full_name']
+ project_code = proj['name']
+ if entity.entity_type.lower() == 'project' and with_childrens == False:
+ return {
+ 'success': True,
+ 'message': 'Nothing was created'
+ }
+ data = {
+ "root": os.environ["AVALON_PROJECTS"],
+ "project": {
+ "name": project_name,
+ "code": project_code
+ }
+ }
+ all_entities = []
+ all_entities.append(entity)
+ if with_childrens:
+ all_entities = self.get_notask_children(entity)
- #######################################################################
-
- # 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': 'Creating Folders.'
- })
- })
-
+ av_project = None
try:
- self.importable = set([])
- # self.importable = []
-
- self.Anatomy = pype.Anatomy
-
- project = entities[0]['project']
-
- paths_collected = set([])
-
- # get all child entities separately/unique
- for entity in entities:
- self.getShotAsset(entity)
-
- for ent in self.importable:
- self.log.info("{}".format(ent['name']))
-
- for entity in self.importable:
- print(entity['name'])
-
- anatomy = pype.Anatomy
- parents = entity['link']
-
- hierarchy_names = []
- for p in parents[1:-1]:
- hierarchy_names.append(p['name'])
-
- if hierarchy_names:
- # hierarchy = os.path.sep.join(hierarchy)
- hierarchy = os.path.join(*hierarchy_names)
-
- template_data = {"project": {"name": project['full_name'],
- "code": project['name']},
- "asset": entity['name'],
- "hierarchy": hierarchy}
-
- for task in entity['children']:
- if task['object_type']['name'] == 'Task':
- self.log.info('child: {}'.format(task['name']))
- template_data['task'] = task['name']
- anatomy_filled = anatomy.format(template_data)
- paths_collected.add(anatomy_filled.work.folder)
- paths_collected.add(anatomy_filled.publish.folder)
-
- for path in paths_collected:
- self.log.info(path)
- try:
- os.makedirs(path)
- except OSError as error:
- if error.errno != errno.EEXIST:
- raise
-
- job['status'] = 'done'
- session.commit()
-
- except ValueError as ve:
- job['status'] = 'failed'
- session.commit()
- message = str(ve)
- self.log.error('Error during syncToAvalon: {}'.format(message))
-
+ self.db.install()
+ self.db.Session['AVALON_PROJECT'] = project_name
+ av_project = self.db.find_one({'type': 'project'})
+ template_work = av_project['config']['template']['work']
+ template_publish = av_project['config']['template']['publish']
+ self.db.uninstall()
except Exception:
- job['status'] = 'failed'
- session.commit()
+ anatomy = pype.Anatomy
+ template_work = anatomy.avalon.work
+ template_publish = anatomy.avalon.publish
- #######################################################################
+ collected_paths = []
+ presets = self.get_presets()
+ for entity in all_entities:
+ if entity.entity_type.lower() == 'project':
+ continue
+ ent_data = data.copy()
+
+ asset_name = entity['name']
+ ent_data['asset'] = asset_name
+
+ parents = entity['link']
+ hierarchy_names = [p['name'] for p in parents[1:-1]]
+ hierarchy = ''
+ if hierarchy_names:
+ hierarchy = os.path.sep.join(hierarchy_names)
+ ent_data['hierarchy'] = hierarchy
+
+ tasks_created = False
+ if entity['children']:
+ for child in entity['children']:
+ if child['object_type']['name'].lower() != 'task':
+ continue
+ tasks_created = True
+ task_type_name = child['type']['name'].lower()
+ task_data = ent_data.copy()
+ task_data['task'] = child['name']
+ possible_apps = presets.get(task_type_name, [])
+ template_work_created = False
+ template_publish_created = False
+ apps = []
+ for app in possible_apps:
+ try:
+ app_data = avalonlib.get_application(app)
+ app_dir = app_data['application_dir']
+ except ValueError:
+ app_dir = app
+ apps.append(app_dir)
+
+ # Template wok
+ if '{app}' in template_work:
+ for app in apps:
+ template_work_created = True
+ app_data = task_data.copy()
+ app_data['app'] = app
+ collected_paths.append(
+ self.compute_template(
+ template_work, app_data
+ )
+ )
+ if template_work_created is False:
+ collected_paths.append(
+ self.compute_template(template_work, task_data)
+ )
+ # Template publish
+ if '{app}' in template_publish:
+ for app in apps:
+ template_publish_created = True
+ app_data = task_data.copy()
+ app_data['app'] = app
+ collected_paths.append(
+ self.compute_template(
+ template_publish, app_data, True
+ )
+ )
+ if template_publish_created is False:
+ collected_paths.append(
+ self.compute_template(
+ template_publish, task_data, True
+ )
+ )
+
+ if not tasks_created:
+ # create path for entity
+ collected_paths.append(
+ self.compute_template(template_work, ent_data)
+ )
+ collected_paths.append(
+ self.compute_template(template_publish, ent_data)
+ )
+ if len(collected_paths) > 0:
+ self.log.info('Creating folders:')
+ for path in set(collected_paths):
+ self.log.info(path)
+ if not os.path.exists(path):
+ os.makedirs(path)
return {
'success': True,
'message': 'Created Folders Successfully!'
}
+ def get_notask_children(self, entity):
+ output = []
+ if entity.get('object_type', {}).get(
+ 'name', entity.entity_type
+ ).lower() == 'task':
+ return output
+ else:
+ output.append(entity)
+ if entity['children']:
+ for child in entity['children']:
+ output.extend(self.get_notask_children(child))
+ return output
+
+ def get_presets(self):
+ fpath_items = [pypelib.get_presets_path(), 'tools', 'sw_folders.json']
+ filepath = os.path.normpath(os.path.sep.join(fpath_items))
+ presets = dict()
+ try:
+ with open(filepath) as data_file:
+ presets = json.load(data_file)
+ except Exception as e:
+ self.log.warning('Wasn\'t able to load presets')
+ return dict(presets)
+
+ def template_format(self, template, data):
+
+ partial_data = PartialDict(data)
+
+ # remove subdict items from string (like 'project[name]')
+ subdict = PartialDict()
+ count = 1
+ store_pattern = 5*'_'+'{:0>3}'
+ regex_patern = "\{\w*\[[^\}]*\]\}"
+ matches = re.findall(regex_patern, template)
+
+ for match in matches:
+ key = store_pattern.format(count)
+ subdict[key] = match
+ template = template.replace(match, '{'+key+'}')
+ count += 1
+ # solve fillind keys with optional keys
+ solved = self._solve_with_optional(template, partial_data)
+ # try to solve subdict and replace them back to string
+ for k, v in subdict.items():
+ try:
+ v = v.format_map(data)
+ except (KeyError, TypeError):
+ pass
+ subdict[k] = v
+
+ return solved.format_map(subdict)
+
+ def _solve_with_optional(self, template, data):
+ # Remove optional missing keys
+ pattern = re.compile(r"(<.*?[^{0]*>)[^0-9]*?")
+ invalid_optionals = []
+ for group in pattern.findall(template):
+ try:
+ group.format(**data)
+ except KeyError:
+ invalid_optionals.append(group)
+ for group in invalid_optionals:
+ template = template.replace(group, "")
+
+ solved = template.format_map(data)
+
+ # solving after format optional in second round
+ for catch in re.compile(r"(<.*?[^{0]*>)[^0-9]*?").findall(solved):
+ if "{" in catch:
+ # remove all optional
+ solved = solved.replace(catch, "")
+ else:
+ # Remove optional symbols
+ solved = solved.replace(catch, catch[1:-1])
+
+ return solved
+
+ def compute_template(self, str, data, task=False):
+ first_result = self.template_format(str, data)
+ if first_result == first_result.split('{')[0]:
+ return os.path.normpath(first_result)
+ if task:
+ return os.path.normpath(first_result.split('{')[0])
+
+ index = first_result.index('{')
+
+ regex = '\{\w*[^\}]*\}'
+ match = re.findall(regex, first_result[index:])[0]
+ without_missing = str.split(match)[0].split('}')
+ output_items = []
+ for part in without_missing:
+ if '{' in part:
+ output_items.append(part + '}')
+ return os.path.normpath(
+ self.template_format(''.join(output_items), data)
+ )
+
+
+class PartialDict(dict):
+ def __getitem__(self, item):
+ out = super().__getitem__(item)
+ if isinstance(out, dict):
+ return '{'+item+'}'
+ return out
+
+ def __missing__(self, key):
+ return '{'+key+'}'
+
def register(session, **kw):
'''Register plugin. Called when used as an plugin.'''
diff --git a/pype/ftrack/actions/action_create_sw_folders.py b/pype/ftrack/actions/action_create_sw_folders.py
deleted file mode 100644
index f6b14cb764..0000000000
--- a/pype/ftrack/actions/action_create_sw_folders.py
+++ /dev/null
@@ -1,155 +0,0 @@
-import os
-import sys
-import json
-import argparse
-import logging
-
-import ftrack_api
-from avalon import lib as avalonlib
-from avalon.tools.libraryloader.io_nonsingleton import DbConnector
-from pype import lib as pypelib
-from pype.ftrack import BaseAction
-
-
-class CreateSWFolders(BaseAction):
- '''Edit meta data action.'''
-
- #: Action identifier.
- identifier = 'create.sw.folders'
- #: Action label.
- label = 'Create SW Folders'
- #: Action description.
- description = 'Creates folders for all SW in project'
-
-
- def __init__(self, session):
- super().__init__(session)
- self.avalon_db = DbConnector()
- self.avalon_db.install()
-
- def discover(self, session, entities, event):
- ''' Validation '''
-
- return True
-
- def launch(self, session, entities, event):
- if len(entities) != 1:
- self.log.warning(
- 'There are more entities in selection!'
- )
- return False
- entity = entities[0]
- if entity.entity_type.lower() != 'task':
- self.log.warning(
- 'Selected entity is not Task!'
- )
- return False
- asset = entity['parent']
- project = asset['project']
-
- project_name = project["full_name"]
- self.avalon_db.Session['AVALON_PROJECT'] = project_name
- av_project = self.avalon_db.find_one({'type': 'project'})
- av_asset = self.avalon_db.find_one({
- 'type': 'asset',
- 'name': asset['name']
- })
-
- templates = av_project["config"]["template"]
- template = templates.get("work", None)
- if template is None:
- return False
-
-
- data = {
- "root": os.environ["AVALON_PROJECTS"],
- "project": {
- "name": project_name,
- "code": project["name"]
- },
- "hierarchy": av_asset['data']['hierarchy'],
- "asset": asset['name'],
- "task": entity['name'],
- }
-
- apps = []
- if '{app}' in template:
- # Apps in project
- for app in av_project['data']['applications']:
- app_data = avalonlib.get_application(app)
- app_dir = app_data['application_dir']
- if app_dir not in apps:
- apps.append(app_dir)
- # Apps in presets
- path_items = [pypelib.get_presets_path(), 'tools', 'sw_folders.json']
- filepath = os.path.sep.join(path_items)
-
- presets = dict()
- try:
- with open(filepath) as data_file:
- presets = json.load(data_file)
- except Exception as e:
- self.log.warning('Wasn\'t able to load presets')
- preset_apps = presets.get(project_name, presets.get('__default__', []))
- for app in preset_apps:
- if app not in apps:
- apps.append(app)
-
- # Create folders for apps
- for app in apps:
- data['app'] = app
- self.log.info('Created folder for app {}'.format(app))
- path = os.path.normpath(template.format(**data))
- if os.path.exists(path):
- continue
- os.makedirs(path)
-
- return True
-
-
-def register(session, **kw):
- '''Register plugin. Called when used as an plugin.'''
-
- if not isinstance(session, ftrack_api.session.Session):
- return
-
- CreateSWFolders(session).register()
-
-
-def main(arguments=None):
- '''Set up logging and register action.'''
- if arguments is None:
- arguments = []
-
- parser = argparse.ArgumentParser()
- # Allow setting of logging level from arguments.
- loggingLevels = {}
- for level in (
- logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
- logging.ERROR, logging.CRITICAL
- ):
- loggingLevels[logging.getLevelName(level).lower()] = level
-
- parser.add_argument(
- '-v', '--verbosity',
- help='Set the logging output verbosity.',
- choices=loggingLevels.keys(),
- default='info'
- )
- namespace = parser.parse_args(arguments)
-
- # Set up basic logging
- logging.basicConfig(level=loggingLevels[namespace.verbosity])
-
- session = ftrack_api.Session()
- register(session)
-
- # Wait for events
- logging.info(
- 'Registered actions and listening for events. Use Ctrl-C to abort.'
- )
- session.event_hub.wait()
-
-
-if __name__ == '__main__':
- raise SystemExit(main(sys.argv[1:]))
diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py
index 30acd4d849..7a087ca825 100644
--- a/pype/ftrack/lib/ftrack_app_handler.py
+++ b/pype/ftrack/lib/ftrack_app_handler.py
@@ -200,21 +200,39 @@ class AppAction(BaseHandler):
application = avalonlib.get_application(os.environ["AVALON_APP_NAME"])
- data = {"project": {"name": entity['project']['full_name'],
- "code": entity['project']['name']},
- "task": entity['name'],
- "asset": entity['parent']['name'],
- "app": application["application_dir"],
- "hierarchy": hierarchy}
- try:
- anatomy = anatomy.format(data)
- except Exception as e:
- self.log.error(
- "{0} Error in anatomy.format: {1}".format(__name__, e)
+ data = {
+ "root": os.environ["AVALON_PROJECTS"],
+ "project": {
+ "name": entity['project']['full_name'],
+ "code": entity['project']['name']
+ },
+ "task": entity['name'],
+ "asset": entity['parent']['name'],
+ "app": application["application_dir"],
+ "hierarchy": hierarchy,
+ }
+
+ av_project = database[project_name].find_one({"type": 'project'})
+ templates = None
+ if av_project:
+ work_template = av_project.get('config', {}).get('template', {}).get(
+ 'work', None
)
- os.environ["AVALON_WORKDIR"] = os.path.join(
- anatomy.work.root, anatomy.work.folder
- )
+ work_template = None
+ try:
+ work_template = work_template.format(**data)
+ except Exception:
+ try:
+ anatomy = anatomy.format(data)
+ work_template = os.path.join(
+ anatomy.work.root,
+ anatomy.work.folder
+ )
+ except Exception as e:
+ self.log.error(
+ "{0} Error in anatomy.format: {1}".format(__name__, e)
+ )
+ os.environ["AVALON_WORKDIR"] = os.path.normpath(work_template)
# collect all parents from the task
parents = []