diff --git a/pype/ftrack/actions/action_application_loader.py b/pype/ftrack/actions/action_application_loader.py index 1b0f48f9be..06a3f9c92e 100644 --- a/pype/ftrack/actions/action_application_loader.py +++ b/pype/ftrack/actions/action_application_loader.py @@ -4,7 +4,7 @@ import time from pype.ftrack import AppAction from avalon import lib from pypeapp import Logger -from pype import lib as pypelib +from pype.lib import get_all_avalon_projects log = Logger().get_logger(__name__) @@ -15,10 +15,7 @@ def registerApp(app, session): try: variant = app['name'].split("_")[1] except Exception: - log.warning(( - '"{0}" - App "name" and "variant" is not separated by "_"' - ' (variant is not set)' - ).format(app['name'])) + pass abspath = lib.which_app(app['name']) if abspath is None: @@ -47,18 +44,28 @@ def registerApp(app, session): icon, description, preactions ).register() + if not variant: + log.info('- Variant is not set') + def register(session): - projects = pypelib.get_all_avalon_projects() + # WARNING getting projects only helps to check connection to mongo + # - without will `discover` of ftrack apps actions take ages + result = get_all_avalon_projects() apps = [] - appNames = [] - # Get all application from all projects - for project in projects: - for app in project['config']['apps']: - if app['name'] not in appNames: - appNames.append(app['name']) - apps.append(app) + + launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") + for file in os.listdir(launchers_path): + filename, ext = os.path.splitext(file) + if ext.lower() != ".toml": + continue + loaded_data = toml.load(os.path.join(launchers_path, file)) + app_data = { + "name": filename, + "label": loaded_data.get("label", filename) + } + apps.append(app_data) apps = sorted(apps, key=lambda x: x['name']) app_counter = 0 @@ -68,5 +75,8 @@ def register(session): if app_counter%5 == 0: time.sleep(0.1) app_counter += 1 - except Exception as e: - log.exception("'{0}' - not proper App ({1})".format(app['name'], e)) + except Exception as exc: + log.exception( + "\"{}\" - not a proper App ({})".format(app['name'], str(exc)), + exc_info=True + ) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index b677529ec3..962dc1165c 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -116,13 +116,13 @@ def import_to_avalon( # not override existing templates! templates = av_project['config'].get('template', None) if templates is not None: - for key, value in config['template'].items(): + for key, value in proj_config['template'].items(): if ( key in templates and templates[key] is not None and templates[key] != value ): - config['template'][key] = templates[key] + proj_config['template'][key] = templates[key] projectId = av_project['_id'] @@ -142,7 +142,7 @@ def import_to_avalon( {'_id': ObjectId(projectId)}, {'$set': { 'name': project_name, - 'config': config, + 'config': proj_config, 'data': data }} ) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index fcec20cf6f..7389d1c2c3 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -221,10 +221,15 @@ class AppAction(BaseHandler): anatomy = anatomy.format(data) work_template = anatomy["work"]["folder"] - except Exception as e: - self.log.exception( - "{0} Error in anatomy.format: {1}".format(__name__, e) + except Exception as exc: + msg = "{} Error in anatomy.format: {}".format( + __name__, str(exc) ) + self.log.error(msg, exc_info=True) + return { + 'success': False, + 'message': msg + } workdir = os.path.normpath(work_template) os.environ["AVALON_WORKDIR"] = workdir diff --git a/pype/ftrack/lib/ftrack_base_handler.py b/pype/ftrack/lib/ftrack_base_handler.py index 5f219c1af8..1bb3eb7a8f 100644 --- a/pype/ftrack/lib/ftrack_base_handler.py +++ b/pype/ftrack/lib/ftrack_base_handler.py @@ -75,7 +75,7 @@ class BaseHandler(object): self.type, label) ) except Exception as e: - self.log.exception('{} "{}" - Registration failed ({})'.format( + self.log.error('{} "{}" - Registration failed ({})'.format( self.type, label, str(e)) ) return wrapper_register @@ -84,25 +84,26 @@ class BaseHandler(object): def launch_log(self, func): @functools.wraps(func) def wrapper_launch(*args, **kwargs): - label = self.__class__.__name__ if hasattr(self, 'label'): - if self.variant is None: - label = self.label - else: + if hasattr(self, 'variant'): label = '{} {}'.format(self.label, self.variant) + else: + label = self.label + else: + label = self.__class__.__name__ + self.log.info(('{} "{}": Launched').format(self.type, label)) try: - self.log.info(('{} "{}": Launched').format(self.type, label)) - result = func(*args, **kwargs) - self.log.info(('{} "{}": Finished').format(self.type, label)) - return result - except Exception as e: - msg = '{} "{}": Failed ({})'.format(self.type, label, str(e)) - self.log.exception(msg) + return func(*args, **kwargs) + except Exception as exc: + msg = '{} "{}": Failed ({})'.format(self.type, label, str(exc)) + self.log.error(msg, exc_info=True) return { 'success': False, 'message': msg } + finally: + self.log.info(('{} "{}": Finished').format(self.type, label)) return wrapper_launch @property @@ -230,7 +231,7 @@ class BaseHandler(object): # Get entity type and make sure it is lower cased. Most places except # the component tab in the Sidebar will use lower case notation. entity_type = entity.get('entityType').replace('_', '').lower() - + if session is None: session = self.session diff --git a/pype/lib.py b/pype/lib.py index e443881d39..66cef40674 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -45,13 +45,21 @@ def get_hierarchy(asset_name=None): if not asset_name: asset_name = io.Session.get("AVALON_ASSET", os.environ["AVALON_ASSET"]) - asset = io.find_one({ + asset_entity = io.find_one({ "type": 'asset', "name": asset_name }) + not_set = "PARENTS_NOT_SET" + entity_parents = entity.get("data", {}).get("parents", not_set) + + # If entity already have parents then just return joined + if entity_parents != not_set: + return "/".join(entity_parents) + + # Else query parents through visualParents and store result to entity hierarchy_items = [] - entity = asset + entity = asset_entity while True: parent_id = entity.get("data", {}).get("visualParent") if not parent_id: @@ -59,6 +67,14 @@ def get_hierarchy(asset_name=None): entity = io.find_one({"_id": parent_id}) hierarchy_items.append(entity["name"]) + # Add parents to entity data for next query + entity_data = asset_entity.get("data", {}) + entity_data["parents"] = hierarchy_items + io.update_many( + {"_id": asset_entity["_id"]}, + {"$set": {"data": entity_data}} + ) + return "/".join(hierarchy_items) diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index 834455168a..2e7ccdd675 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -4,7 +4,17 @@ from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish +from .workio import ( + open, + save, + current_file, + has_unsaved_changes, + file_extensions, + work_root +) + from .. import api + from .menu import ( install as menu_install, _update_menu_task_label @@ -17,6 +27,17 @@ import hiero log = Logger().get_logger(__name__, "nukestudio") +__all__ = [ + # Workfiles API + "open", + "save", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root", + ] + + AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") PARENT_DIR = os.path.dirname(__file__) diff --git a/pype/nukestudio/workio.py b/pype/nukestudio/workio.py new file mode 100644 index 0000000000..7fbd85a708 --- /dev/null +++ b/pype/nukestudio/workio.py @@ -0,0 +1,61 @@ +"""Host API required Work Files tool""" +import os +import hiero + + +def file_extensions(): + return [".hrox"] + + +def has_unsaved_changes(): + return hiero.core.projects()[-1] + + +def save(filepath): + project = hiero.core.projects()[-1] + + if project: + project.saveAs(filepath) + else: + project = hiero.core.newProject() + project.saveAs(filepath) + + +def open(filepath): + try: + hiero.core.openProject(filepath) + return True + except Exception as e: + try: + from PySide.QtGui import * + from PySide.QtCore import * + except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + prompt = "Cannot open the selected file: `{}`".format(e) + hiero.core.log.error(prompt) + dialog = QMessageBox.critical( + hiero.ui.mainWindow(), "Error", unicode(prompt)) + + +def current_file(): + import os + import hiero + + current_file = hiero.core.projects()[-1].path() + normalised = os.path.normpath(current_file) + + # Unsaved current file + if normalised is '': + return "NOT SAVED" + + return normalised + + + +def work_root(): + from avalon import api + + return os.path.normpath(api.Session["AVALON_WORKDIR"]).replace("\\", "/") diff --git a/pype/plugins/maya/publish/validate_attributes.py b/pype/plugins/maya/publish/validate_attributes.py index 8b0f14b8b2..654df1ce72 100644 --- a/pype/plugins/maya/publish/validate_attributes.py +++ b/pype/plugins/maya/publish/validate_attributes.py @@ -66,7 +66,7 @@ class ValidateAttributes(pyblish.api.ContextPlugin): ) # Get invalid attributes. - nodes = [pm.PyNode(x) for x in instance] + nodes = pm.ls() for node in nodes: name = node.name(stripNamespace=True) if name not in attributes.keys(): diff --git a/pype/plugins/nuke/publish/remove_ouput_node.py b/pype/plugins/nuke/publish/remove_ouput_node.py index a5e80e511b..12361595fe 100644 --- a/pype/plugins/nuke/publish/remove_ouput_node.py +++ b/pype/plugins/nuke/publish/remove_ouput_node.py @@ -7,7 +7,7 @@ class RemoveOutputNode(pyblish.api.ContextPlugin): """ label = 'Output Node Remove' - order = pyblish.api.IntegratorOrder + order = pyblish.api.IntegratorOrder + 0.4 families = ["workfile"] hosts = ['nuke'] diff --git a/pype/plugins/nuke/publish/validate_rendered_frames.py b/pype/plugins/nuke/publish/validate_rendered_frames.py index 968afe2a5b..85cbe7b2c0 100644 --- a/pype/plugins/nuke/publish/validate_rendered_frames.py +++ b/pype/plugins/nuke/publish/validate_rendered_frames.py @@ -11,7 +11,7 @@ class RepairCollectionAction(pyblish.api.Action): icon = "wrench" def process(self, context, plugin): - self.log.info(context[0][1]) + self.log.info(context[0][0]) files_remove = [os.path.join(context[0].data["outputDir"], f) for r in context[0].data.get("representations", []) for f in r.get("files", []) @@ -20,7 +20,7 @@ class RepairCollectionAction(pyblish.api.Action): for f in files_remove: os.remove(f) self.log.debug("removing file: {}".format(f)) - context[0][1]["render"].setValue(True) + context[0][0]["render"].setValue(True) self.log.info("Rendering toggled ON")