diff --git a/pype/ftrack/actions/ftrack_action_handler.py b/pype/ftrack/actions/ftrack_action_handler.py index 56af11b557..4aec8430c4 100644 --- a/pype/ftrack/actions/ftrack_action_handler.py +++ b/pype/ftrack/actions/ftrack_action_handler.py @@ -6,6 +6,7 @@ import platform import ftrack_api from avalon import io, lib import acre +from pype.ftrack import ftrack_utils from pype import api as pype @@ -384,6 +385,27 @@ 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() + + 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, 'message': "Launching {0}".format(self.label) 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..87e7b35799 --- /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 ExtractAssProxy(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)) 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 "