From ee4a90baf892baf3359bb6c15cd529e5f550f3c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 7 Jan 2019 17:05:42 +0100 Subject: [PATCH 1/5] Added show_interface to event handler. Sync to avalon modified, need add checking statuses of tasks --- pype/ftrack/events/event_sync_to_avalon.py | 137 +++++++++++++++------ pype/ftrack/events/ftrack_event_handler.py | 42 ++++--- 2 files changed, 126 insertions(+), 53 deletions(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index 77b036880c..d1d6519a53 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -9,10 +9,10 @@ from avalon.vendor import toml from bson.objectid import ObjectId from pype.ftrack import ftrack_utils + class Sync_to_Avalon(BaseEvent): def launch(self, session, entities, event): - self.ca_mongoid = 'avalon_mongo_id' # If mongo_id textfield has changed: RETURN! # - infinite loop @@ -21,7 +21,7 @@ class Sync_to_Avalon(BaseEvent): if self.ca_mongoid in ent['keys']: return self.proj = None - + self.errors = [] # get project for entity in entities: try: @@ -32,10 +32,12 @@ class Sync_to_Avalon(BaseEvent): break # check if project is set to auto-sync - if (self.proj is None or + if ( + self.proj is None or 'avalon_auto_sync' not in self.proj['custom_attributes'] or - self.proj['custom_attributes']['avalon_auto_sync'] is False): - return + self.proj['custom_attributes']['avalon_auto_sync'] is False + ): + return # check if project have Custom Attribute 'avalon_mongo_id' if self.ca_mongoid not in self.proj['custom_attributes']: @@ -51,13 +53,18 @@ class Sync_to_Avalon(BaseEvent): # get avalon project if possible io.install() try: - self.avalon_project = io.find_one({"_id": ObjectId(self.projectId)}) + self.avalon_project = io.find_one({ + "_id": ObjectId(self.projectId) + }) except: self.avalon_project = None importEntities = [] if self.avalon_project is None: - self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]}) + self.avalon_project = io.find_one({ + "type": "project", + "name": self.proj["full_name"] + }) if self.avalon_project is None: importEntities.append(self.proj) else: @@ -69,9 +76,10 @@ class Sync_to_Avalon(BaseEvent): if entity.entity_type.lower() in ['task']: entity = entity['parent'] - try: - mongo_id = entity['custom_attributes'][self.ca_mongoid] - except: + if ( + 'custom_attributes' not in entity or + self.ca_mongoid not in entity['custom_attributes'] + ): message = "Custom attribute '{}' for '{}' is not created or don't have set permissions for API".format(self.ca_mongoid, entity.entity_type) self.log.warning(message) self.show_message(event, message, False) @@ -93,13 +101,25 @@ class Sync_to_Avalon(BaseEvent): except ValueError as ve: message = str(ve) - self.show_message(event, message, False) + items = [{ + 'label': 'Error', + 'type': 'textarea', + 'name': 'error', + 'value': message + }] + self.show_interface(event, items) self.log.warning(message) except Exception as e: message = str(e) ftrack_message = "SyncToAvalon event ended with unexpected error please check log file for more information." - self.show_message(event, ftrack_message, False) + items = [{ + 'label': 'Error', + 'type': 'textarea', + 'name': 'error', + 'value': ftrack_message + }] + self.show_interface(event, items) self.log.error(message) io.uninstall() @@ -122,21 +142,26 @@ class Sync_to_Avalon(BaseEvent): if self.avalon_project is None: inventory.save(name, config, template) - self.avalon_project = io.find_one({'type': 'project', 'name': name}) + self.avalon_project = io.find_one({'type': type, 'name': name}) elif self.avalon_project['name'] != name: - raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(self.avalon_project['name'], name)) + entity['name'] = self.avalon_project['name'] + session.commit() + + msg = 'You can\'t change name {} to {}, avalon wouldn\'t work properly!\nName was changed back!'.format(self.avalon_project['name'], name) + self.errors.append(msg) + return self.projectId = self.avalon_project['_id'] - data = ftrack_utils.get_data(self, entity, session,self.custom_attributes) + data = ftrack_utils.get_data(self, entity, session, self.custom_attributes) io.update_many( {"_id": ObjectId(self.projectId)}, - {'$set':{ - 'name':name, - 'config':config, - 'data':data, + {'$set': { + 'name': name, + 'config': config, + 'data': data, }}) entity['custom_attributes'][self.ca_mongoid] = str(self.projectId) @@ -146,7 +171,7 @@ class Sync_to_Avalon(BaseEvent): if self.avalon_project is None: self.importToAvalon(session, self.proj) - data = ftrack_utils.get_data(self, entity, session,self.custom_attributes) + data = ftrack_utils.get_data(self, entity, session, self.custom_attributes) # return if entity is silo if len(data['parents']) == 0: @@ -171,30 +196,64 @@ class Sync_to_Avalon(BaseEvent): if avalon_asset is None: mongo_id = inventory.create_asset(name, silo, data, ObjectId(self.projectId)) # Raise error if it seems to be different ent. with same name - elif (avalon_asset['data']['parents'] != data['parents'] or - avalon_asset['silo'] != silo): - raise ValueError('In Avalon DB already exists entity with name "{0}"'.format(name)) - elif avalon_asset['name'] != entity['name']: - raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please set name back'.format(avalon_asset['name'], name)) - elif avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']: - old_path = "/".join(avalon_asset['data']['parents']) - new_path = "/".join(data['parents']) - raise ValueError('You can\'t move with entities. Entity "{}" was moved from "{}" to "{}" , avalon DB won\'t work properly'.format(avalon_asset['name'], old_path, new_path)) + elif ( + avalon_asset['data']['parents'] != data['parents'] or + avalon_asset['silo'] != silo + ): + msg = 'In Avalon DB already exists entity with name "{0}"'.format(name) + self.errors.append(msg) + return + else: + if avalon_asset['name'] != entity['name']: + self.assetNamer(session, entity, avalon_asset) + if avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']: + old_path = "/".join(avalon_asset['data']['parents']) + new_path = "/".join(data['parents']) + msg = 'You can\'t move with entities. Entity "{}" was moved from "{}" to "{}" , avalon DB won\'t work properly'.format(avalon_asset['name'], old_path, new_path) + self.errors.append(msg) + + if len(self.errors) > 0: + return io.update_many( {"_id": ObjectId(mongo_id)}, - {'$set':{ - 'name':name, - 'silo':silo, - 'data':data, + {'$set': { + 'name': name, + 'silo': silo, + 'data': data, 'parent': ObjectId(self.projectId)}}) entity['custom_attributes'][self.ca_mongoid] = str(mongo_id) + def checkChilds(self, entity): + if entity['children']: + childs = entity['children'] + for child in childs: + if child.entity_type.lower() == 'task': + pass + else: + self.checkChilds() + + def assetNamer(self, session, entity, asset): + ability = True + if entity['children']: + childs = entity['children'] + for child in childs: + if child.entity_type.lower() == 'task': + pass + + if ability is True: + return + msg = 'You can\'t change name {} to {}, avalon wouldn\'t work properly!\nPlease create new entity.\nName was changed back!'.format(avalon_asset['name'], name) + entity['name'] = asset['name'] + session.commit() + self.errors.append(msg) + def setAvalonAttributes(self): self.custom_attributes = [] - all_avalon_attr = self.session.query('CustomAttributeGroup where name is "avalon"').one() + query = 'CustomAttributeGroup where name is "avalon"' + all_avalon_attr = self.session.query(query).one() for cust_attr in all_avalon_attr['custom_attribute_configurations']: if 'avalon_' not in cust_attr['key']: self.custom_attributes.append(cust_attr) @@ -210,10 +269,13 @@ class Sync_to_Avalon(BaseEvent): self.session, *args ) return - + def _translate_event(self, session, event): - exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog'] - _selection = event['data'].get('entities',[]) + exceptions = [ + 'assetversion', 'job', 'user', 'reviewsessionobject', 'timer', + 'socialfeed', 'timelog' + ] + _selection = event['data'].get('entities', []) _entities = list() for entity in _selection: @@ -227,6 +289,7 @@ class Sync_to_Avalon(BaseEvent): return [_entities, event] + def register(session, **kw): '''Register plugin. Called when used as an plugin.''' diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/events/ftrack_event_handler.py index e6d942af06..0cb53b74a9 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/events/ftrack_event_handler.py @@ -1,19 +1,7 @@ # :coding: utf-8 # :copyright: Copyright (c) 2017 ftrack -import os -import logging -import getpass -# import platform import ftrack_api -import toml -from avalon import io, lib, pipeline -from avalon import session as sess -import acre - -from app.api import ( - Templates, - Logger -) +from app.api import Logger class BaseEvent(object): @@ -47,7 +35,7 @@ class BaseEvent(object): def _translate_event(self, session, event): '''Return *event* translated structure to be used with the API.''' - _selection = event['data'].get('entities',[]) + _selection = event['data'].get('entities', []) _entities = list() for entity in _selection: @@ -119,7 +107,7 @@ class BaseEvent(object): ''' raise NotImplementedError() - def show_message(self, event, input_message, result = False): + def show_message(self, event, input_message, result=False): """ Shows message to user who triggered event - event - just source of user id @@ -137,6 +125,8 @@ class BaseEvent(object): return user_id = event['source']['user']['id'] + target = 'applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) + self.session.event_hub.publish( ftrack_api.event.base.Event( topic='ftrack.action.trigger-user-interface', @@ -145,7 +135,27 @@ class BaseEvent(object): success=result, message=message ), - target='applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) + target=target + ), + on_error='ignore' + ) + + def show_interface(self, event, items): + """ + Shows interface to user who triggered event + - 'items' must be list containing Ftrack interface items + """ + user_id = event['source']['user']['id'] + target = 'applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) + + self.session.event_hub.publish( + ftrack_api.event.base.Event( + topic='ftrack.action.trigger-user-interface', + data=dict( + type='widget', + items=items + ), + target=target ), on_error='ignore' ) From 44e1d87b52332321c1b0903e68cf14ddb0c054da Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Jan 2019 13:25:16 +0100 Subject: [PATCH 2/5] sync to avalon shows interface to user when error has happened so he can't continue to do another errors --- pype/ftrack/events/event_sync_to_avalon.py | 117 +++++++++++++++------ pype/ftrack/ftrack_utils.py | 17 +++ 2 files changed, 100 insertions(+), 34 deletions(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index d1d6519a53..0f2cb9d29f 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -10,6 +10,11 @@ from bson.objectid import ObjectId from pype.ftrack import ftrack_utils +class ExpectedError(Exception): + def __init__(self, *args, **kwargs): + super().__init__(self, *args, **kwargs) + + class Sync_to_Avalon(BaseEvent): def launch(self, session, entities, event): @@ -96,19 +101,21 @@ class Sync_to_Avalon(BaseEvent): io.install() try: for entity in importEntities: - self.importToAvalon(session, entity) + self.importToAvalon(session, event, entity) session.commit() - except ValueError as ve: - message = str(ve) - items = [{ - 'label': 'Error', - 'type': 'textarea', - 'name': 'error', - 'value': message - }] + except ExpectedError as ee: + items = [] + for error in self.errors: + info = { + 'label': 'Error', + 'type': 'textarea', + 'name': 'error', + 'value': error + } + items.append(info) + self.log.warning(error) self.show_interface(event, items) - self.log.warning(message) except Exception as e: message = str(e) @@ -126,7 +133,7 @@ class Sync_to_Avalon(BaseEvent): return - def importToAvalon(self, session, entity): + def importToAvalon(self, session, event, entity): if self.ca_mongoid not in entity['custom_attributes']: raise ValueError("Custom attribute '{}' for '{}' is not created or don't have set permissions for API".format(self.ca_mongoid, entity['name'])) @@ -169,12 +176,14 @@ class Sync_to_Avalon(BaseEvent): return if self.avalon_project is None: - self.importToAvalon(session, self.proj) + self.importToAvalon(session, event, self.proj) data = ftrack_utils.get_data(self, entity, session, self.custom_attributes) - # return if entity is silo + # only check name if entity is silo if len(data['parents']) == 0: + if self.checkSilo(entity, event, session) is False: + raise ExpectedError return else: silo = data['parents'][0] @@ -205,16 +214,20 @@ class Sync_to_Avalon(BaseEvent): return else: if avalon_asset['name'] != entity['name']: - self.assetNamer(session, entity, avalon_asset) + if self.checkChilds(entity) is False: + msg = 'You can\'t change name {} to {}, avalon wouldn\'t work properly!\n\nName was changed back!\n\nCreate new entity if you want to change name.'.format(avalon_asset['name'], entity['name']) + entity['name'] = avalon_asset['name'] + session.commit() + self.errors.append(msg) if avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']: old_path = "/".join(avalon_asset['data']['parents']) new_path = "/".join(data['parents']) - msg = 'You can\'t move with entities. Entity "{}" was moved from "{}" to "{}" , avalon DB won\'t work properly'.format(avalon_asset['name'], old_path, new_path) + msg = 'You can\'t move with entities.\nEntity "{}" was moved from "{}" to "{}"\n\nAvalon won\'t work properly, please move them back!'.format(avalon_asset['name'], old_path, new_path) self.errors.append(msg) if len(self.errors) > 0: - return + raise ExpectedError io.update_many( {"_id": ObjectId(mongo_id)}, @@ -227,28 +240,64 @@ class Sync_to_Avalon(BaseEvent): entity['custom_attributes'][self.ca_mongoid] = str(mongo_id) def checkChilds(self, entity): - if entity['children']: - childs = entity['children'] - for child in childs: - if child.entity_type.lower() == 'task': - pass + if (entity.entity_type.lower() != 'task' and 'children' not in entity): + return True + childs = entity['children'] + for child in childs: + if child.entity_type.lower() == 'task': + config = ftrack_utils.get_config_data() + if 'sync_to_avalon' in config: + config = config['sync_to_avalon'] + if 'statuses_name_change' in config: + available_statuses = config['statuses_name_change'] else: - self.checkChilds() + available_statuses = [] + ent_status = child['status']['name'].lower() + if ent_status not in available_statuses: + return False + # If not task go deeper + elif self.checkChilds(child) is False: + return False + # If everything is allright + return True - def assetNamer(self, session, entity, asset): - ability = True - if entity['children']: - childs = entity['children'] - for child in childs: - if child.entity_type.lower() == 'task': - pass + def checkSilo(self, entity, event, session): + changes = event['data']['entities'][0]['changes'] + if 'name' not in changes: + return True + new_name = changes['name']['new'] + old_name = changes['name']['old'] - if ability is True: - return - msg = 'You can\'t change name {} to {}, avalon wouldn\'t work properly!\nPlease create new entity.\nName was changed back!'.format(avalon_asset['name'], name) - entity['name'] = asset['name'] - session.commit() + if 'children' not in entity or len(entity['children']) < 1: + return True + + if self.checkChilds(entity) is True: + self.updateSilo(old_name, new_name) + return True + + new_found = 0 + old_found = 0 + for asset in io.find({'silo': new_name}): + new_found += 1 + for asset in io.find({'silo': old_name}): + old_found += 1 + + if new_found > 0 or old_found == 0: + return True + + # If any condition is possible, show error to user and change name back + msg = 'You can\'t change name {} to {}, avalon wouldn\'t work properly!\n\nName was changed back!\n\nCreate new entity if you want to change name.'.format(old_name, new_name) self.errors.append(msg) + entity['name'] = old_name + session.commit() + + return False + + def updateSilo(self, old, new): + io.update_many( + {'silo': old}, + {'$set': {'silo': new}} + ) def setAvalonAttributes(self): self.custom_attributes = [] diff --git a/pype/ftrack/ftrack_utils.py b/pype/ftrack/ftrack_utils.py index caaeb6c707..2177b3f8c3 100644 --- a/pype/ftrack/ftrack_utils.py +++ b/pype/ftrack/ftrack_utils.py @@ -1,6 +1,7 @@ import os import sys import re +import json from pprint import * import ftrack_api @@ -13,6 +14,22 @@ from app.api import Logger log = Logger.getLogger(__name__) + +def get_config_data(): + templates = os.environ['PYPE_STUDIO_TEMPLATES'] + path_items = [templates, 'presets', 'ftrack', 'ftrack_config.json'] + filepath = os.path.sep.join(path_items) + data = dict() + try: + with open(filepath) as data_file: + data = json.load(data_file) + + except Exception as e: + msg = 'Loading "Ftrack Config file" Failed. Please check log for more information. Times are set to default.' + log.warning("{} - {}".format(msg, str(e))) + + return data + def get_data(parent, entity, session, custom_attributes): entity_type = entity.entity_type From e25c840a29fb96a72f611520b6773bce213c6c53 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Jan 2019 19:39:38 +0100 Subject: [PATCH 3/5] add option to work with arnold standins and simple proxies --- pype/plugins/global/publish/integrate.py | 3 +- pype/plugins/maya/create/create_ass.py | 32 ++++ pype/plugins/maya/load/load_ass.py | 148 ++++++++++++++++++ pype/plugins/maya/publish/collect_ass.py | 35 +++++ pype/plugins/maya/publish/extract_ass.py | 47 ++++++ pype/plugins/maya/publish/extract_assproxy.py | 73 +++++++++ 6 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 pype/plugins/maya/create/create_ass.py create mode 100644 pype/plugins/maya/load/load_ass.py create mode 100644 pype/plugins/maya/publish/collect_ass.py create mode 100644 pype/plugins/maya/publish/extract_ass.py create mode 100644 pype/plugins/maya/publish/extract_assproxy.py diff --git a/pype/plugins/global/publish/integrate.py b/pype/plugins/global/publish/integrate.py index 8a1d2224cb..b63c1693eb 100644 --- a/pype/plugins/global/publish/integrate.py +++ b/pype/plugins/global/publish/integrate.py @@ -37,7 +37,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "yeticache", "nukescript", "review", - "scene"] + "scene", + "ass"] def process(self, instance): diff --git a/pype/plugins/maya/create/create_ass.py b/pype/plugins/maya/create/create_ass.py new file mode 100644 index 0000000000..3423648c40 --- /dev/null +++ b/pype/plugins/maya/create/create_ass.py @@ -0,0 +1,32 @@ +from collections import OrderedDict + +import avalon.maya + +from maya import cmds + + +class CreateAss(avalon.maya.Creator): + """Arnold Archive""" + + name = "ass" + label = "Ass StandIn" + family = "ass" + icon = "cube" + + def process(self): + instance = super(CreateAss, self).process() + + data = OrderedDict(**self.data) + + nodes = list() + + if (self.options or {}).get("useSelection"): + nodes = cmds.ls(selection=True) + + cmds.sets(nodes, rm=instance) + + assContent = cmds.sets(name="content_SET") + assProxy = cmds.sets(name="proxy_SET", empty=True) + cmds.sets([assContent, assProxy], forceElement=instance) + + self.data = data diff --git a/pype/plugins/maya/load/load_ass.py b/pype/plugins/maya/load/load_ass.py new file mode 100644 index 0000000000..814639a4d9 --- /dev/null +++ b/pype/plugins/maya/load/load_ass.py @@ -0,0 +1,148 @@ +from avalon import api +import pype.maya.plugin +import os + + +class AssProxyLoader(pype.maya.plugin.ReferenceLoader): + """Load the Proxy""" + + families = ["ass"] + representations = ["ass"] + + label = "Reference .ASS standin with Proxy" + order = -10 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, data): + + import maya.cmds as cmds + from avalon import maya + import pymel.core as pm + + with maya.maintained_selection(): + + groupName = "{}:{}".format(namespace, name) + path = self.fname + proxyPath = os.path.splitext(path)[0] + ".ma" + + nodes = cmds.file(proxyPath, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=groupName) + + cmds.makeIdentity(groupName, apply=False, rotate=True, translate=True, scale=True) + + # Set attributes + proxyShape = pm.ls(nodes, type="mesh")[0] + proxyShape = pm.ls(nodes, type="mesh")[0] + + proxyShape.aiTranslator.set('procedural') + proxyShape.dso.set(path) + proxyShape.aiOverrideShaders.set(0) + + + self[:] = nodes + + return nodes + + def switch(self, container, representation): + self.update(container, representation) + + +class AssStandinLoader(api.Loader): + """Load .ASS file as standin""" + + families = ["ass"] + representations = ["ass"] + + label = "Load .ASS file as standin" + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, data): + + import maya.cmds as cmds + import avalon.maya.lib as lib + from avalon.maya.pipeline import containerise + import mtoa.ui.arnoldmenu + import pymel.core as pm + + + asset = context['asset']['name'] + namespace = namespace or lib.unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + # cmds.loadPlugin("gpuCache", quiet=True) + + # Root group + label = "{}:{}".format(namespace, name) + root = pm.group(name=label, empty=True) + + # Create transform with shape + transform_name = label + "_ASS" + # transform = pm.createNode("transform", name=transform_name, + # parent=root) + + standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) + standin = standinShape.getParent() + standin.rename(transform_name) + + pm.parent(standin, root) + + # Set the standin filepath + standinShape.dso.set(self.fname) + + + # Lock parenting of the transform and standin + cmds.lockNode([root, standin], lock=True) + + nodes = [root, standin] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + import pymel.core as pm + + path = api.get_representation_path(representation) + + # Update the standin + members = pm.sets(container['objectName'], query=True) + standins = pm.ls(members, type="AiStandIn", long=True) + + assert len(caches) == 1, "This is a bug" + + for standin in standins: + standin.cacheFileName.set(path) + + container = pm.PyNode(container["objectName"]) + container.representation.set(str(representation["_id"])) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + import maya.cmds as cmds + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass diff --git a/pype/plugins/maya/publish/collect_ass.py b/pype/plugins/maya/publish/collect_ass.py new file mode 100644 index 0000000000..c0174e7026 --- /dev/null +++ b/pype/plugins/maya/publish/collect_ass.py @@ -0,0 +1,35 @@ +from maya import cmds +import pymel.core as pm + +import pyblish.api +import avalon.api + +class CollectAssData(pyblish.api.InstancePlugin): + """Collect Ass data + + """ + + order = pyblish.api.CollectorOrder + 0.2 + label = 'Collect Ass' + families = ["ass"] + + def process(self, instance): + + + context = instance.context + + objsets = instance.data['setMembers'] + + for objset in objsets: + members = cmds.sets(objset, query=True) + if members is None: + self.log.warning("Skipped empty instance: \"%s\" " % objset) + continue + if objset == "content_SET": + instance.data['setMembers'] = members + elif objset == "proxy_SET": + assert len(members) == 1, "You have multiple proxy meshes, please only use one" + instance.data['proxy'] = members + + + self.log.debug("data: {}".format(instance.data)) diff --git a/pype/plugins/maya/publish/extract_ass.py b/pype/plugins/maya/publish/extract_ass.py new file mode 100644 index 0000000000..14b548b928 --- /dev/null +++ b/pype/plugins/maya/publish/extract_ass.py @@ -0,0 +1,47 @@ +import os + +import avalon.maya +import pype.api + +from maya import cmds + + +class ExtractAssStandin(pype.api.Extractor): + """Extract the content of the instance to a ass file + + Things to pay attention to: + - If animation is toggled, are the frames correct + - + """ + + label = "Ass Standin (.ass)" + hosts = ["maya"] + families = ["ass"] + + def process(self, instance): + + staging_dir = self.staging_dir(instance) + file_name = "{}.ass".format(instance.name) + file_path = os.path.join(staging_dir, file_name) + + # Write out .ass file + self.log.info("Writing: '%s'" % file_path) + with avalon.maya.maintained_selection(): + self.log.info("Writing: {}".format(instance.data["setMembers"])) + cmds.select(instance.data["setMembers"], noExpand=True) + cmds.arnoldExportAss( filename=file_path, + selected=True, + asciiAss=True, + shadowLinks=True, + lightLinks=True, + boundingBox=True + ) + + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(file_name) + + self.log.info("Extracted instance '%s' to: %s" + % (instance.name, staging_dir)) diff --git a/pype/plugins/maya/publish/extract_assproxy.py b/pype/plugins/maya/publish/extract_assproxy.py new file mode 100644 index 0000000000..31f5e0393b --- /dev/null +++ b/pype/plugins/maya/publish/extract_assproxy.py @@ -0,0 +1,73 @@ +import os + +from maya import cmds +import contextlib + +import avalon.maya +import pype.api +import pype.maya.lib as lib + + +class ExtractModel(pype.api.Extractor): + """Extract proxy model as Maya Ascii to use as arnold standin + + + """ + + order = pype.api.Extractor.order + 0.2 + label = "Ass Proxy (Maya ASCII)" + hosts = ["maya"] + families = ["ass"] + + def process(self, instance): + + @contextlib.contextmanager + def unparent(root): + """Temporarily unparent `root`""" + parent = cmds.listRelatives(root, parent=True) + if parent: + cmds.parent(root, world=True) + yield + self.log.info("{} - {}".format(root, parent)) + cmds.parent(root, parent) + else: + yield + + + # Define extract output file path + stagingdir = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(stagingdir, filename) + + # Perform extraction + self.log.info("Performing extraction..") + + # Get only the shape contents we need in such a way that we avoid + # taking along intermediateObjects + members = instance.data['proxy'] + members = cmds.ls(members, + dag=True, + transforms=True, + noIntermediate=True) + self.log.info(members) + + with avalon.maya.maintained_selection(): + with unparent(members[0]): + cmds.select(members, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=False, + constraints=False, + expressions=False, + constructionHistory=False) + + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) From a0ffa586cebed54b283bc6a5c6299711117c2521 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 10 Jan 2019 12:42:32 +0100 Subject: [PATCH 4/5] hotfix / rename ass extractor --- pype/plugins/maya/publish/extract_assproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/maya/publish/extract_assproxy.py b/pype/plugins/maya/publish/extract_assproxy.py index 31f5e0393b..87e7b35799 100644 --- a/pype/plugins/maya/publish/extract_assproxy.py +++ b/pype/plugins/maya/publish/extract_assproxy.py @@ -8,7 +8,7 @@ import pype.api import pype.maya.lib as lib -class ExtractModel(pype.api.Extractor): +class ExtractAssProxy(pype.api.Extractor): """Extract proxy model as Maya Ascii to use as arnold standin From 6298e63e418444a469eb381fc878cc4abe4d898f Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 10 Jan 2019 13:44:52 +0100 Subject: [PATCH 5/5] hotfix / ass publishing invalidated looks publishing --- pype/plugins/maya/publish/validate_look_sets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pype/plugins/maya/publish/validate_look_sets.py b/pype/plugins/maya/publish/validate_look_sets.py index 6ef333486d..1819602430 100644 --- a/pype/plugins/maya/publish/validate_look_sets.py +++ b/pype/plugins/maya/publish/validate_look_sets.py @@ -70,6 +70,13 @@ class ValidateLookSets(pyblish.api.InstancePlugin): # check if any objectSets are not present ion the relationships missing_sets = [s for s in sets if s not in relationships] + + for set in missing_sets: + if set.endswith("_SET"): + missing_sets.remove(set) + cls.log.info("Missing Sets " + "'{}'".format(missing_sets)) + if missing_sets: # A set of this node is not coming along, this is wrong! cls.log.error("Missing sets '{}' for node "