From 668eb24969102874a7df0907c12a057b7f2f437e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 7 Jan 2019 17:38:50 +0100 Subject: [PATCH 1/6] added hierarchical check for custom attrbiutes --- pype/ftrack/ftrack_utils.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pype/ftrack/ftrack_utils.py b/pype/ftrack/ftrack_utils.py index caaeb6c707..7038c0adf8 100644 --- a/pype/ftrack/ftrack_utils.py +++ b/pype/ftrack/ftrack_utils.py @@ -13,6 +13,7 @@ from app.api import Logger log = Logger.getLogger(__name__) + def get_data(parent, entity, session, custom_attributes): entity_type = entity.entity_type @@ -22,13 +23,20 @@ def get_data(parent, entity, session, custom_attributes): for cust_attr in custom_attributes: key = cust_attr['key'] - if cust_attr['entity_type'].lower() in ['asset']: + if ( + cust_attr['is_hierarchical'] is True or + cust_attr['entity_type'].lower() in ['asset'] or + ( + cust_attr['entity_type'].lower() in ['show'] and + entity_type.lower() == 'project' + ) + ): data[key] = entity['custom_attributes'][key] - elif cust_attr['entity_type'].lower() in ['show'] and entity_type.lower() == 'project': - data[key] = entity['custom_attributes'][key] - - elif cust_attr['entity_type'].lower() in ['task'] and entity_type.lower() != 'project': + elif ( + cust_attr['entity_type'].lower() in ['task'] and + entity_type.lower() != 'project' + ): # Put space between capitals (e.g. 'AssetBuild' -> 'Asset Build') entity_type_full = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) # Get object id of entity type From 787422ab0127ef30907139e0f4da12c8db435aa6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Jan 2019 16:38:32 +0100 Subject: [PATCH 2/6] initial --- pype/ftrack/actions/ftrack_action_handler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/actions/ftrack_action_handler.py b/pype/ftrack/actions/ftrack_action_handler.py index d147a2630b..215d054e4d 100644 --- a/pype/ftrack/actions/ftrack_action_handler.py +++ b/pype/ftrack/actions/ftrack_action_handler.py @@ -10,7 +10,7 @@ import toml from avalon import io, lib, pipeline from avalon import session as sess import acre - +from pype.ftrack import ftrack_utils from pype import api as pype @@ -354,6 +354,11 @@ class AppAction(object): self.log.info('Starting timer for task: ' + task['name']) user.start_timer(task, force=True) + # Change status of task to In progress + config = ftrack_utils.get_config_data() + statuses = config['sync_to_avalon']['statuses_name_change'] + status = config['status_on_app_launch'] + return { 'success': True, 'message': "Launching {0}".format(self.label) From af3a918f24ce93bbf97b9d9382286dcd9eeb9843 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Jan 2019 17:16:48 +0100 Subject: [PATCH 3/6] app launch changes status if current status is same as in config 'statuses_name_change' --- pype/ftrack/actions/ftrack_action_handler.py | 28 +++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pype/ftrack/actions/ftrack_action_handler.py b/pype/ftrack/actions/ftrack_action_handler.py index 215d054e4d..bfa297790e 100644 --- a/pype/ftrack/actions/ftrack_action_handler.py +++ b/pype/ftrack/actions/ftrack_action_handler.py @@ -345,19 +345,35 @@ class AppAction(object): } pass - - # 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() + query_user = 'User where username is "{}"'.format(username) + query_task = 'Task where id is {}'.format(entity['id']) + user = session.query(query_user).one() + task = session.query(query_task).one() self.log.info('Starting timer for task: ' + task['name']) user.start_timer(task, force=True) # Change status of task to In progress config = ftrack_utils.get_config_data() - statuses = config['sync_to_avalon']['statuses_name_change'] - status = config['status_on_app_launch'] + + if ( + 'status_on_app_launch' in config and + 'sync_to_avalon' in config and + 'statuses_name_change' in config['sync_to_avalon'] + ): + statuses = config['sync_to_avalon']['statuses_name_change'] + if entity['status']['name'].lower() in statuses: + status_name = config['status_on_app_launch'] + + try: + query = 'Status where name is "{}"'.format(status_name) + status = session.query(query).one() + task['status'] = status + session.commit() + except Exception as e: + msg = "Status '{}' in config wasn't found on Ftrack".format(status_name) + self.log.warning(msg) return { 'success': True, From e25c840a29fb96a72f611520b6773bce213c6c53 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Jan 2019 19:39:38 +0100 Subject: [PATCH 4/6] 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 5/6] 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 6/6] 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 "