diff --git a/changelog.md b/changelog.md index 46cceb9fdc..bdee041615 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,47 @@ # Pype changelog # Welcome to pype changelog +## 2.3.0 ## +_release date: 6 Oct 2019_ + +**new**: +- _(maya)_ support for yeti rigs and yeti caches +- _(maya)_ validator for comparing arbitrary attributes against ftrack +- _(pype)_ burnins can now show current date and time +- _(muster)_ pools can now be set in render globals in maya +- _(pype)_ Rest API has been implemented in beta stage +- _(nuke)_ LUT loader has been added +- _(pype)_ rudimentary user module has been added as preparation for user management +- _(pype)_ a simple logging GUI has been added to pype tray +- _(nuke)_ nuke can now bake input process into mov +- _(maya)_ imported models now have selection handle displayed by defaulting +- _(avalon)_ it's is now possible to load multiple assets at once using loader +- _(maya)_ added ability to automatically connect yeti rig to a mesh upon loading + +**changed**: +- _(ftrack)_ event server now runs two parallel processes and is able to keep queue of events to process. +- _(nuke)_ task name is now added to all rendered subsets +- _(pype)_ adding more families to standalone publisher +- _(pype)_ standalone publisher now uses pyblish-lite +- _(pype)_ standalone publisher can now create review quicktimes +- _(ftrack)_ queries to ftrack were sped up +- _(ftrack)_ multiple ftrack action have been deprecated +- _(avalon)_ avalon upstream has been updated to 5.5.0 +- _(nukestudio)_ published transforms can now be animated +- + +**fix**: +- _(maya)_ fps popup button didn't work in some cases +- _(maya)_ geometry instances and references in maya were losing shader assignments +- _(muster)_ muster rendering templates were not working correctly +- _(maya)_ arnold tx texture conversion wasn't respecting colorspace set by the artist +- _(pype)_ problems with avalon db sync +- _(maya)_ ftrack was rounding FPS making it inconsistent +- _(pype)_ wrong icon names in Creator +- _(maya)_ scene inventory wasn't showing anything if representation was removed from database after it's been loaded to the scene +- _(nukestudio)_ multiple bugs squashed +- _(loader)_ loader was taking long time to show all the loading action when first launcher in maya + ## 2.2.0 ## _release date: 8 Sept 2019_ diff --git a/pype/__init__.py b/pype/__init__.py index c2311dd528..91b72d7de5 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -9,7 +9,7 @@ from pypeapp import config import logging log = logging.getLogger(__name__) -__version__ = "2.1.0" +__version__ = "2.3.0" PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index a25866be65..3fe65bca11 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -101,6 +101,8 @@ class Sync_to_Avalon(BaseEvent): avalon_project = result['project'] except Exception as e: + session.reset() # reset session to clear it + message = str(e) title = 'Hey You! Unknown Error has been raised! (*look below*)' ftrack_message = ( diff --git a/pype/ftrack/events/event_thumbnail_updates.py b/pype/ftrack/events/event_thumbnail_updates.py index ae6f8adb5e..a74aad6bd9 100644 --- a/pype/ftrack/events/event_thumbnail_updates.py +++ b/pype/ftrack/events/event_thumbnail_updates.py @@ -20,7 +20,8 @@ class ThumbnailEvents(BaseEvent): if parent.get('thumbnail') and not task.get('thumbnail'): task['thumbnail'] = parent['thumbnail'] self.log.info('>>> Updated thumbnail on [ %s/%s ]'.format( - parent['name'], task['name'])) + parent['name'], task['name'] + )) # Update task thumbnail from published version # if (entity['entityType'] == 'assetversion' and @@ -32,18 +33,24 @@ class ThumbnailEvents(BaseEvent): version = session.get('AssetVersion', entity['entityId']) thumbnail = version.get('thumbnail') - task = version['task'] - if thumbnail: - task['thumbnail'] = thumbnail - task['parent']['thumbnail'] = thumbnail - self.log.info('>>> Updating thumbnail for task and shot\ - [ {} ]'.format(task['name'])) + parent = version['asset']['parent'] + task = version['task'] + parent['thumbnail_id'] = version['thumbnail_id'] + if parent.entity_type.lower() == "project": + name = parent["full_name"] + else: + name = parent["name"] + msg = '>>> Updating thumbnail for shot [ {} ]'.format(name) + + if task: + task['thumbnail_id'] = version['thumbnail_id'] + msg += " and task [ {} ]".format(task["name"]) + + self.log.info(msg) session.commit() - pass - def register(session, plugins_presets): '''Register plugin. Called when used as an plugin.''' diff --git a/pype/ftrack/ftrack_server/event_server_cli.py b/pype/ftrack/ftrack_server/event_server_cli.py index ec7cac9d6a..751d91ad4b 100644 --- a/pype/ftrack/ftrack_server/event_server_cli.py +++ b/pype/ftrack/ftrack_server/event_server_cli.py @@ -533,7 +533,9 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - if hasattr(signal, "SIGKILL"): - signal.signal(signal.SIGKILL, signal_handler) + try: + signal.signal(signal.SIGKILL, signal_handler) + except OSError: + pass sys.exit(main(sys.argv)) diff --git a/pype/ftrack/ftrack_server/sub_event_processor.py b/pype/ftrack/ftrack_server/sub_event_processor.py index 9444fe3ff0..4772c1e85e 100644 --- a/pype/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/ftrack/ftrack_server/sub_event_processor.py @@ -47,7 +47,8 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - if hasattr(signal, "SIGKILL"): + try: signal.signal(signal.SIGKILL, signal_handler) - + except OSError: + pass sys.exit(main(sys.argv)) diff --git a/pype/ftrack/ftrack_server/sub_event_storer.py b/pype/ftrack/ftrack_server/sub_event_storer.py index 6e30fb99e2..a49561cfaf 100644 --- a/pype/ftrack/ftrack_server/sub_event_storer.py +++ b/pype/ftrack/ftrack_server/sub_event_storer.py @@ -112,7 +112,8 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - if hasattr(signal, "SIGKILL"): + try: signal.signal(signal.SIGKILL, signal_handler) - + except OSError: + pass sys.exit(main(sys.argv)) diff --git a/pype/ftrack/ftrack_server/sub_legacy_server.py b/pype/ftrack/ftrack_server/sub_legacy_server.py index c162b1abe1..01b1563db5 100644 --- a/pype/ftrack/ftrack_server/sub_legacy_server.py +++ b/pype/ftrack/ftrack_server/sub_legacy_server.py @@ -94,7 +94,8 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - if hasattr(signal, "SIGKILL"): + try: signal.signal(signal.SIGKILL, signal_handler) - + except OSError: + pass sys.exit(main(sys.argv)) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 8ef8ccf26f..28e813c449 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -963,7 +963,7 @@ class BuildWorkfile(WorkfileSettings): def process(self, regex_filter=None, version=None, - representations=["exr", "dpx", "lutJson"]): + representations=["exr", "dpx", "lutJson", "mov", "preview"]): """ A short description. @@ -1020,6 +1020,8 @@ class BuildWorkfile(WorkfileSettings): version=version, representations=representations) + log.info("__ subsets: `{}`".format(subsets)) + nodes_backdrop = list() for name, subset in subsets.items(): diff --git a/pype/plugins/maya/load/load_ass.py b/pype/plugins/maya/load/load_ass.py index 979d4b5767..2960e4403e 100644 --- a/pype/plugins/maya/load/load_ass.py +++ b/pype/plugins/maya/load/load_ass.py @@ -1,7 +1,6 @@ from avalon import api import pype.maya.plugin import os -import pymel.core as pm from pypeapp import config @@ -70,6 +69,7 @@ class AssProxyLoader(pype.maya.plugin.ReferenceLoader): import os from maya import cmds + import pymel.core as pm node = container["objectName"] diff --git a/pype/plugins/maya/load/load_gpucache.py b/pype/plugins/maya/load/load_gpucache.py index b98ca8b7f4..9e7938777e 100644 --- a/pype/plugins/maya/load/load_gpucache.py +++ b/pype/plugins/maya/load/load_gpucache.py @@ -2,7 +2,6 @@ from avalon import api import pype.maya.plugin import os from pypeapp import config -import pymel.core as pm reload(config) diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index e2d94ac82e..e95ea6cd8f 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -1,5 +1,3 @@ -import pymel.core as pc - from avalon import api from Qt import QtWidgets @@ -14,6 +12,8 @@ class ImagePlaneLoader(api.Loader): color = "orange" def load(self, context, name, namespace, data): + import pymel.core as pc + new_nodes = [] image_plane_depth = 1000 diff --git a/pype/plugins/maya/load/load_reference.py b/pype/plugins/maya/load/load_reference.py index a754c3be98..55db019cf4 100644 --- a/pype/plugins/maya/load/load_reference.py +++ b/pype/plugins/maya/load/load_reference.py @@ -1,8 +1,6 @@ - import pype.maya.plugin import os from pypeapp import config -import pymel.core as pm reload(config) import pype.maya.plugin reload(pype.maya.plugin) @@ -20,9 +18,10 @@ class ReferenceLoader(pype.maya.plugin.ReferenceLoader): color = "orange" def process_reference(self, context, name, namespace, data): - import maya.cmds as cmds from avalon import maya + import pymel.core as pm + try: family = context["representation"]["context"]["family"] diff --git a/pype/plugins/maya/load/load_yeti_rig.py b/pype/plugins/maya/load/load_yeti_rig.py index eb75ff6bdc..a3e03e8a6c 100644 --- a/pype/plugins/maya/load/load_yeti_rig.py +++ b/pype/plugins/maya/load/load_yeti_rig.py @@ -1,9 +1,17 @@ -import pype.maya.plugin import os +from collections import defaultdict + from pypeapp import config +import pype.maya.plugin +from pype.maya import lib class YetiRigLoader(pype.maya.plugin.ReferenceLoader): + """ + This loader will load Yeti rig. You can select something in scene and if it + has same ID as mesh published with rig, their shapes will be linked + together. + """ families = ["yetiRig"] representations = ["ma"] @@ -18,6 +26,32 @@ class YetiRigLoader(pype.maya.plugin.ReferenceLoader): import maya.cmds as cmds from avalon import maya + # get roots of selected hierarchies + selected_roots = [] + for sel in cmds.ls(sl=True, long=True): + selected_roots.append(sel.split("|")[1]) + + # get all objects under those roots + selected_hierarchy = [] + for root in selected_roots: + selected_hierarchy.append(cmds.listRelatives( + root, + allDescendents=True) or []) + + # flatten the list and filter only shapes + shapes_flat = [] + for root in selected_hierarchy: + shapes = cmds.ls(root, long=True, type="mesh") or [] + for shape in shapes: + shapes_flat.append(shape) + + # create dictionary of cbId and shape nodes + scene_lookup = defaultdict(list) + for node in shapes_flat: + cb_id = lib.get_id(node) + scene_lookup[cb_id] = node + + # load rig with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, @@ -26,6 +60,20 @@ class YetiRigLoader(pype.maya.plugin.ReferenceLoader): groupReference=True, groupName="{}:{}".format(namespace, name)) + # for every shape node we've just loaded find matching shape by its + # cbId in selection. If found outMesh of scene shape will connect to + # inMesh of loaded shape. + for destination_node in nodes: + source_node = scene_lookup[lib.get_id(destination_node)] + if source_node: + self.log.info("found: {}".format(source_node)) + self.log.info( + "creating connection to {}".format(destination_node)) + + cmds.connectAttr("{}.outMesh".format(source_node), + "{}.inMesh".format(destination_node), + force=True) + groupName = "{}:{}".format(namespace, name) presets = config.get_presets(project=os.environ['AVALON_PROJECT']) @@ -38,6 +86,4 @@ class YetiRigLoader(pype.maya.plugin.ReferenceLoader): c[0], c[1], c[2]) self[:] = nodes - self.log.info("Yeti Rig Connection Manager will be available soon") - return nodes diff --git a/pype/plugins/maya/publish/extract_yeti_rig.py b/pype/plugins/maya/publish/extract_yeti_rig.py index b575c07cf4..892bc0bea6 100644 --- a/pype/plugins/maya/publish/extract_yeti_rig.py +++ b/pype/plugins/maya/publish/extract_yeti_rig.py @@ -126,6 +126,18 @@ class ExtractYetiRig(pype.api.Extractor): with open(settings_path, "w") as fp: json.dump(settings, fp, ensure_ascii=False) + # add textures to transfers + if 'transfers' not in instance.data: + instance.data['transfers'] = [] + + for resource in instance.data.get('resources', []): + for file in resource['files']: + src = file + dst = os.path.join(image_search_path, os.path.basename(file)) + instance.data['transfers'].append([src, dst]) + + self.log.info("adding transfer {} -> {}". format(src, dst)) + # Ensure the imageSearchPath is being remapped to the publish folder attr_value = {"%s.imageSearchPath" % n: str(image_search_path) for n in yeti_nodes} diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 2ebbfde551..f9eb126772 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -196,12 +196,14 @@ class CollectPlatesData(api.InstancePlugin): thumb_file = head + ".png" thumb_path = os.path.join(staging_dir, thumb_file) + thumb_frame = instance.data["sourceIn"] + ((instance.data["sourceOut"] - instance.data["sourceIn"])/2) - thumbnail = item.thumbnail(instance.data["sourceIn"]).save( + thumbnail = item.thumbnail(thumb_frame).save( thumb_path, format='png' ) - self.log.debug("__ thumbnail: {}".format(thumbnail)) + self.log.debug("__ sourceIn: `{}`".format(instance.data["sourceIn"])) + self.log.debug("__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) thumb_representation = { 'files': thumb_file, diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py index 9fab0f0741..f9032b2ca4 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -106,7 +106,6 @@ class CollectReviews(api.InstancePlugin): def create_thumbnail(self, instance): item = instance.data["item"] - source_in = instance.data["sourceIn"] source_path = instance.data["sourcePath"] source_file = os.path.basename(source_path) @@ -119,11 +118,17 @@ class CollectReviews(api.InstancePlugin): thumb_file = head + ".png" thumb_path = os.path.join(staging_dir, thumb_file) self.log.debug("__ thumb_path: {}".format(thumb_path)) - self.log.debug("__ source_in: {}".format(source_in)) - thumbnail = item.thumbnail(source_in).save( + + thumb_frame = instance.data["sourceIn"] + ((instance.data["sourceOut"] - instance.data["sourceIn"])/2) + + thumbnail = item.thumbnail(thumb_frame).save( thumb_path, format='png' ) + + self.log.debug("__ sourceIn: `{}`".format(instance.data["sourceIn"])) + self.log.debug("__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) + self.log.debug("__ thumbnail: {}".format(thumbnail)) thumb_representation = {