From 6664cd963e40dc19c9741261c2c0744ea575fdec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Jun 2020 18:51:36 +0200 Subject: [PATCH 01/13] input resolutions are converted to integer --- pype/plugins/global/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 141617bda9..97e88243bb 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -610,8 +610,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Skipped using instance's resolution full_input_path_single_file = temp_data["full_input_path_single_file"] input_data = pype.lib.ffprobe_streams(full_input_path_single_file)[0] - input_width = input_data["width"] - input_height = input_data["height"] + input_width = int(input_data["width"]) + input_height = int(input_data["height"]) self.log.debug("pixel_aspect: `{}`".format(pixel_aspect)) self.log.debug("input_width: `{}`".format(input_width)) From c620ade4bc45ae25602f870c389343cf0899acd1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Jun 2020 18:51:47 +0200 Subject: [PATCH 02/13] output resolution is converted to int too for sure --- pype/plugins/global/publish/extract_review.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 97e88243bb..89cdce95db 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -631,6 +631,9 @@ class ExtractReview(pyblish.api.InstancePlugin): output_width = input_width output_height = input_height + output_width = int(output_width) + output_height = int(output_height) + self.log.debug( "Output resolution is {}x{}".format(output_width, output_height) ) From 569cb0a8c34711b2722df9ec65056ac7ef985d49 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 22 Jun 2020 13:26:12 +0100 Subject: [PATCH 03/13] Publish review --- .../photoshop/publish/collect_review.py | 36 ++++++ .../photoshop/publish/extract_review.py | 103 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 pype/plugins/photoshop/publish/collect_review.py create mode 100644 pype/plugins/photoshop/publish/extract_review.py diff --git a/pype/plugins/photoshop/publish/collect_review.py b/pype/plugins/photoshop/publish/collect_review.py new file mode 100644 index 0000000000..30042d188b --- /dev/null +++ b/pype/plugins/photoshop/publish/collect_review.py @@ -0,0 +1,36 @@ +import os + +import pythoncom + +import pyblish.api + + +class CollectReview(pyblish.api.ContextPlugin): + """Gather the active document as review instance.""" + + label = "Review" + order = pyblish.api.CollectorOrder + hosts = ["photoshop"] + + def process(self, context): + # Necessary call when running in a different thread which pyblish-qml + # can be. + pythoncom.CoInitialize() + + family = "review" + task = os.getenv("AVALON_TASK", None) + subset = family + task.capitalize() + + file_path = context.data["currentFile"] + base_name = os.path.basename(file_path) + + instance = context.create_instance(subset) + instance.data.update({ + "subset": subset, + "label": base_name, + "name": base_name, + "family": family, + "families": ["ftrack"], + "representations": [], + "asset": os.environ["AVALON_ASSET"] + }) diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py new file mode 100644 index 0000000000..607e039d14 --- /dev/null +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -0,0 +1,103 @@ +import os + +import pype.api +import pype.lib +from avalon import photoshop + + +class ExtractReview(pype.api.Extractor): + """Produce a flattened image file from all instances.""" + + label = "Extract Review" + hosts = ["photoshop"] + families = ["review"] + + def process(self, instance): + + staging_dir = self.staging_dir(instance) + self.log.info("Outputting image to {}".format(staging_dir)) + + layers = [] + for image_instance in instance.context: + if image_instance.data["family"] != "image": + continue + layers.append(image_instance[0]) + + # Perform extraction + output_image = "{} copy.jpg".format( + os.path.splitext(photoshop.app().ActiveDocument.Name)[0] + ) + with photoshop.maintained_visibility(): + # Hide all other layers. + extract_ids = [ + x.id for x in photoshop.get_layers_in_layers(layers) + ] + for layer in photoshop.get_layers_in_document(): + if layer.id in extract_ids: + layer.Visible = True + else: + layer.Visible = False + + photoshop.app().ActiveDocument.SaveAs( + staging_dir, photoshop.com_objects.JPEGSaveOptions(), True + ) + + instance.data["representations"].append({ + "name": "jpg", + "ext": "jpg", + "files": output_image, + "stagingDir": staging_dir + }) + instance.data["stagingDir"] = staging_dir + + # Generate thumbnail. + thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") + args = [ + "ffmpeg", "-y", + "-i", os.path.join(staging_dir, output_image), + "-vf", "scale=300:-1", + "-vframes", "1", + thumbnail_path + ] + output = pype.lib._subprocess(args) + + self.log.debug(output) + + instance.data["representations"].append({ + "name": "thumbnail", + "ext": "jpg", + "files": os.path.basename(thumbnail_path), + "stagingDir": staging_dir, + "tags": ["thumbnail"] + }) + + # Generate mov. + mov_path = os.path.join(staging_dir, "review.mov") + args = [ + "ffmpeg", "-y", + "-i", os.path.join(staging_dir, output_image), + "-vframes", "1", + mov_path + ] + output = pype.lib._subprocess(args) + + self.log.debug(output) + + instance.data["representations"].append({ + "name": "mov", + "ext": "mov", + "files": os.path.basename(mov_path), + "stagingDir": staging_dir, + "frameStart": 1, + "frameEnd": 2, + "fps": 25, + "preview": True, + "tags": ["review", "ftrackreview"] + }) + + # Required for extract_review plugin (L222 onwards). + instance.data["frameStart"] = 1 + instance.data["frameEnd"] = 2 + instance.data["fps"] = 25 + + self.log.info(f"Extracted {instance} to {staging_dir}") From 85a298e4f5867a7bbd7598679496735dd1200f2b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jun 2020 12:30:20 +0100 Subject: [PATCH 04/13] Flag Outdated containers - on startup as message box appears, and outdated containers are coloured red. - on publish the "Validate Containers" errors. - loaded image containers are coloured green. --- pype/hosts/harmony/__init__.py | 100 +++++++++++- .../global/publish/validate_containers.py | 2 +- .../harmony/load/load_imagesequence.py | 147 ++++++++++++++---- 3 files changed, 217 insertions(+), 32 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 169786204e..628397e777 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -1,8 +1,9 @@ import os import sys -from avalon import api, harmony +from avalon import api, io, harmony from avalon.vendor import Qt +import avalon.tools.sceneinventory import pyblish.api from pype import lib @@ -92,6 +93,101 @@ def ensure_scene_settings(): set_scene_settings(valid_settings) +def check_inventory(): + if not lib.any_outdated(): + return + + host = avalon.api.registered_host() + outdated_containers = [] + for container in host.ls(): + representation = container['representation'] + representation_doc = io.find_one( + { + "_id": io.ObjectId(representation), + "type": "representation" + }, + projection={"parent": True} + ) + if representation_doc and not lib.is_latest(representation_doc): + outdated_containers.append(container) + + # Colour nodes. + func = """function func(args){ + for( var i =0; i <= args[0].length - 1; ++i) + { + var red_color = new ColorRGBA(255, 0, 0, 255); + node.setColor(args[0][i], red_color); + } + } + func + """ + outdated_nodes = [x["node"] for x in outdated_containers] + harmony.send({"function": func, "args": [outdated_nodes]}) + + # Warn about outdated containers. + print("Starting new QApplication..") + app = Qt.QtWidgets.QApplication(sys.argv) + + message_box = Qt.QtWidgets.QMessageBox() + message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning) + msg = "There are outdated containers in the scene." + message_box.setText(msg) + message_box.exec_() + + # Garbage collect QApplication. + del app + + +def application_launch(): + ensure_scene_settings() + check_inventory() + + +def export_template(backdrops, nodes, filepath): + func = """function func(args) + { + // Add an extra node just so a new group can be created. + var temp_node = node.add("Top", "temp_note", "NOTE", 0, 0, 0); + var template_group = node.createGroup(temp_node, "temp_group"); + node.deleteNode( template_group + "/temp_note" ); + + // This will make Node View to focus on the new group. + selection.clearSelection(); + selection.addNodeToSelection(template_group); + Action.perform("onActionEnterGroup()", "Node View"); + + // Recreate backdrops in group. + for (var i = 0 ; i < args[0].length; i++) + { + Backdrop.addBackdrop(template_group, args[0][i]); + }; + + // Copy-paste the selected nodes into the new group. + var drag_object = copyPaste.copy(args[1], 1, frame.numberOf, ""); + copyPaste.pasteNewNodes(drag_object, template_group, ""); + + // Select all nodes within group and export as template. + Action.perform( "selectAll()", "Node View" ); + copyPaste.createTemplateFromSelection(args[2], args[3]); + + // Unfocus the group in Node view, delete all nodes and backdrops + // created during the process. + Action.perform("onActionUpToParent()", "Node View"); + node.deleteNode(template_group, true, true); + } + func + """ + harmony.send({ + "function": func, + "args": [ + backdrops, + nodes, + os.path.basename(filepath), + os.path.dirname(filepath) + ] + }) + + def install(): print("Installing Pype config...") @@ -116,7 +212,7 @@ def install(): "instanceToggled", on_pyblish_instance_toggled ) - api.on("application.launched", ensure_scene_settings) + api.on("application.launched", application_launch) def on_pyblish_instance_toggled(instance, old_value, new_value): diff --git a/pype/plugins/global/publish/validate_containers.py b/pype/plugins/global/publish/validate_containers.py index 44cb5def3c..2c7b763f7a 100644 --- a/pype/plugins/global/publish/validate_containers.py +++ b/pype/plugins/global/publish/validate_containers.py @@ -19,7 +19,7 @@ class ValidateContainers(pyblish.api.ContextPlugin): label = "Validate Containers" order = pyblish.api.ValidatorOrder - hosts = ["maya", "houdini", "nuke"] + hosts = ["maya", "houdini", "nuke", "harmony"] optional = True actions = [ShowInventory] diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index 7862e027af..b56dba03d4 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -98,33 +98,63 @@ function import_files(args) transparencyModeAttr.setValue(SGITransparencyMode); if (extension == "psd") transparencyModeAttr.setValue(FlatPSDTransparencyMode); + if (extension == "jpg") + transparencyModeAttr.setValue(LayeredPSDTransparencyMode); node.linkAttr(read, "DRAWING.ELEMENT", uniqueColumnName); - // Create a drawing for each file. - for( var i =0; i <= files.length - 1; ++i) + if (files.length == 1) { - timing = start_frame + i // Create a drawing drawing, 'true' indicate that the file exists. - Drawing.create(elemId, timing, true); + Drawing.create(elemId, 1, true); // Get the actual path, in tmp folder. - var drawingFilePath = Drawing.filename(elemId, timing.toString()); - copyFile( files[i], drawingFilePath ); + var drawingFilePath = Drawing.filename(elemId, "1"); + copyFile(files[0], drawingFilePath); + // Expose the image for the entire frame range. + for( var i =0; i <= frame.numberOf() - 1; ++i) + { + timing = start_frame + i + column.setEntry(uniqueColumnName, 1, timing, "1"); + } + } else { + // Create a drawing for each file. + for( var i =0; i <= files.length - 1; ++i) + { + timing = start_frame + i + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, timing, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, timing.toString()); + copyFile( files[i], drawingFilePath ); - column.setEntry(uniqueColumnName, 1, timing, timing.toString()); + column.setEntry(uniqueColumnName, 1, timing, timing.toString()); + } } + + var green_color = new ColorRGBA(0, 255, 0, 255); + node.setColor(read, green_color); + return read; } import_files """ -replace_files = """function replace_files(args) +replace_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +var TGATransparencyMode = 0; //Premultiplied wih Black +var SGITransparencyMode = 0; //Premultiplied wih Black +var LayeredPSDTransparencyMode = 1; //Straight +var FlatPSDTransparencyMode = 2; //Premultiplied wih White + +function replace_files(args) { var files = args[0]; + MessageLog.trace(files); + MessageLog.trace(files.length); var _node = args[1]; var start_frame = args[2]; var _column = node.linkedColumn(_node, "DRAWING.ELEMENT"); + var elemId = column.getElementIdOfDrawing(_column); // Delete existing drawings. var timings = column.getDrawingTimings(_column); @@ -133,20 +163,62 @@ replace_files = """function replace_files(args) column.deleteDrawingAt(_column, parseInt(timings[i])); } - // Create new drawings. - for( var i =0; i <= files.length - 1; ++i) - { - timing = start_frame + i - // Create a drawing drawing, 'true' indicate that the file exists. - Drawing.create(node.getElementId(_node), timing, true); - // Get the actual path, in tmp folder. - var drawingFilePath = Drawing.filename( - node.getElementId(_node), timing.toString() - ); - copyFile( files[i], drawingFilePath ); - column.setEntry(_column, 1, timing, timing.toString()); + var filename = files[0]; + var pos = filename.lastIndexOf("."); + if( pos < 0 ) + return null; + var extension = filename.substr(pos+1).toLowerCase(); + + if(extension == "jpeg") + extension = "jpg"; + + var transparencyModeAttr = node.getAttr( + _node, frame.current(), "applyMatteToColor" + ); + if (extension == "png") + transparencyModeAttr.setValue(PNGTransparencyMode); + if (extension == "tga") + transparencyModeAttr.setValue(TGATransparencyMode); + if (extension == "sgi") + transparencyModeAttr.setValue(SGITransparencyMode); + if (extension == "psd") + transparencyModeAttr.setValue(FlatPSDTransparencyMode); + if (extension == "jpg") + transparencyModeAttr.setValue(LayeredPSDTransparencyMode); + + if (files.length == 1) + { + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, 1, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, "1"); + copyFile(files[0], drawingFilePath); + MessageLog.trace(files[0]); + MessageLog.trace(drawingFilePath); + // Expose the image for the entire frame range. + for( var i =0; i <= frame.numberOf() - 1; ++i) + { + timing = start_frame + i + column.setEntry(_column, 1, timing, "1"); + } + } else { + // Create a drawing for each file. + for( var i =0; i <= files.length - 1; ++i) + { + timing = start_frame + i + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, timing, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, timing.toString()); + copyFile( files[i], drawingFilePath ); + + column.setEntry(_column, 1, timing, timing.toString()); + } } + + var green_color = new ColorRGBA(0, 255, 0, 255); + node.setColor(_node, green_color); } replace_files """ @@ -156,8 +228,8 @@ class ImageSequenceLoader(api.Loader): """Load images Stores the imported asset in a container named after the asset. """ - families = ["shot", "render"] - representations = ["jpeg", "png"] + families = ["shot", "render", "image"] + representations = ["jpeg", "png", "jpg"] def load(self, context, name=None, namespace=None, data=None): @@ -165,9 +237,18 @@ class ImageSequenceLoader(api.Loader): os.listdir(os.path.dirname(self.fname)) ) files = [] - for f in list(collections[0]): + if collections: + for f in list(collections[0]): + files.append( + os.path.join( + os.path.dirname(self.fname), f + ).replace("\\", "/") + ) + else: files.append( - os.path.join(os.path.dirname(self.fname), f).replace("\\", "/") + os.path.join( + os.path.dirname(self.fname), remainder[0] + ).replace("\\", "/") ) read_node = harmony.send( @@ -190,15 +271,23 @@ class ImageSequenceLoader(api.Loader): def update(self, container, representation): node = container.pop("node") + path = api.get_representation_path(representation) collections, remainder = clique.assemble( - os.listdir( - os.path.dirname(api.get_representation_path(representation)) - ) + os.listdir(os.path.dirname(path)) ) files = [] - for f in list(collections[0]): + if collections: + for f in list(collections[0]): + files.append( + os.path.join( + os.path.dirname(path), f + ).replace("\\", "/") + ) + else: files.append( - os.path.join(os.path.dirname(self.fname), f).replace("\\", "/") + os.path.join( + os.path.dirname(path), remainder[0] + ).replace("\\", "/") ) harmony.send( From bcd81fa934bcf13ca5861f212cb6871fa439e78a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 Jun 2020 12:52:35 +0100 Subject: [PATCH 05/13] Implemented 'camera' asset for Blender --- pype/plugins/blender/create/create_camera.py | 32 +++ pype/plugins/blender/load/load_camera.py | 241 ++++++++++++++++++ pype/plugins/blender/publish/extract_blend.py | 2 +- 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 pype/plugins/blender/create/create_camera.py create mode 100644 pype/plugins/blender/load/load_camera.py diff --git a/pype/plugins/blender/create/create_camera.py b/pype/plugins/blender/create/create_camera.py new file mode 100644 index 0000000000..5817985053 --- /dev/null +++ b/pype/plugins/blender/create/create_camera.py @@ -0,0 +1,32 @@ +"""Create a camera asset.""" + +import bpy + +from avalon import api +from avalon.blender import Creator, lib +import pype.hosts.blender.plugin + + +class CreateCamera(Creator): + """Polygonal static geometry""" + + name = "cameraMain" + label = "Camera" + family = "camera" + icon = "video-camera" + + def process(self): + + asset = self.data["asset"] + subset = self.data["subset"] + name = pype.hosts.blender.plugin.asset_name(asset, subset) + collection = bpy.data.collections.new(name=name) + bpy.context.scene.collection.children.link(collection) + self.data['task'] = api.Session.get('AVALON_TASK') + lib.imprint(collection, self.data) + + if (self.options or {}).get("useSelection"): + for obj in lib.get_selection(): + collection.objects.link(obj) + + return collection diff --git a/pype/plugins/blender/load/load_camera.py b/pype/plugins/blender/load/load_camera.py new file mode 100644 index 0000000000..a69c01e806 --- /dev/null +++ b/pype/plugins/blender/load/load_camera.py @@ -0,0 +1,241 @@ +"""Load a camera asset in Blender.""" + +import logging +from pathlib import Path +from pprint import pformat +from typing import Dict, List, Optional + +from avalon import api, blender +import bpy +import pype.hosts.blender.plugin + +logger = logging.getLogger("pype").getChild("blender").getChild("load_camera") + + +class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader): + """Load a camera from a .blend file. + + Warning: + Loading the same asset more then once is not properly supported at the + moment. + """ + + families = ["camera"] + representations = ["blend"] + + label = "Link Camera" + icon = "code-fork" + color = "orange" + + def _remove(self, objects, lib_container): + + for obj in objects: + bpy.data.cameras.remove(obj.data) + + bpy.data.collections.remove(bpy.data.collections[lib_container]) + + def _process(self, libpath, lib_container, container_name, actions): + + relative = bpy.context.preferences.filepaths.use_relative_paths + with bpy.data.libraries.load( + libpath, link=True, relative=relative + ) as (_, data_to): + data_to.collections = [lib_container] + + scene = bpy.context.scene + + scene.collection.children.link(bpy.data.collections[lib_container]) + + camera_container = scene.collection.children[lib_container].make_local() + + objects_list = [] + + for obj in camera_container.objects: + obj = obj.make_local() + obj.data.make_local() + + if not obj.get(blender.pipeline.AVALON_PROPERTY): + obj[blender.pipeline.AVALON_PROPERTY] = dict() + + avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info.update({"container_name": container_name}) + + + + if actions[0] is not None: + if obj.animation_data is None: + obj.animation_data_create() + obj.animation_data.action = actions[0] + + if actions[1] is not None: + if obj.data.animation_data is None: + obj.data.animation_data_create() + obj.data.animation_data.action = actions[1] + + objects_list.append(obj) + + camera_container.pop(blender.pipeline.AVALON_PROPERTY) + + bpy.ops.object.select_all(action='DESELECT') + + return objects_list + + def process_asset( + self, context: dict, name: str, namespace: Optional[str] = None, + options: Optional[Dict] = None + ) -> Optional[List]: + """ + Arguments: + name: Use pre-defined name + namespace: Use pre-defined namespace + context: Full parenthood of representation to load + options: Additional settings dictionary + """ + + libpath = self.fname + asset = context["asset"]["name"] + subset = context["subset"]["name"] + lib_container = pype.hosts.blender.plugin.asset_name(asset, subset) + container_name = pype.hosts.blender.plugin.asset_name( + asset, subset, namespace + ) + + container = bpy.data.collections.new(lib_container) + container.name = container_name + blender.pipeline.containerise_existing( + container, + name, + namespace, + context, + self.__class__.__name__, + ) + + container_metadata = container.get( + blender.pipeline.AVALON_PROPERTY) + + container_metadata["libpath"] = libpath + container_metadata["lib_container"] = lib_container + + objects_list = self._process( + libpath, lib_container, container_name, (None, None)) + + # Save the list of objects in the metadata container + container_metadata["objects"] = objects_list + + nodes = list(container.objects) + nodes.append(container) + self[:] = nodes + return nodes + + def update(self, container: Dict, representation: Dict): + """Update the loaded asset. + + This will remove all objects of the current collection, load the new + ones and add them to the collection. + If the objects of the collection are used in another collection they + will not be removed, only unlinked. Normally this should not be the + case though. + + Warning: + No nested collections are supported at the moment! + """ + + collection = bpy.data.collections.get( + container["objectName"] + ) + + libpath = Path(api.get_representation_path(representation)) + extension = libpath.suffix.lower() + + logger.info( + "Container: %s\nRepresentation: %s", + pformat(container, indent=2), + pformat(representation, indent=2), + ) + + assert collection, ( + f"The asset is not loaded: {container['objectName']}" + ) + assert not (collection.children), ( + "Nested collections are not supported." + ) + assert libpath, ( + "No existing library file found for {container['objectName']}" + ) + assert libpath.is_file(), ( + f"The file doesn't exist: {libpath}" + ) + assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, ( + f"Unsupported file: {libpath}" + ) + + collection_metadata = collection.get( + blender.pipeline.AVALON_PROPERTY) + collection_libpath = collection_metadata["libpath"] + objects = collection_metadata["objects"] + lib_container = collection_metadata["lib_container"] + + normalized_collection_libpath = ( + str(Path(bpy.path.abspath(collection_libpath)).resolve()) + ) + normalized_libpath = ( + str(Path(bpy.path.abspath(str(libpath))).resolve()) + ) + logger.debug( + "normalized_collection_libpath:\n %s\nnormalized_libpath:\n %s", + normalized_collection_libpath, + normalized_libpath, + ) + if normalized_collection_libpath == normalized_libpath: + logger.info("Library already loaded, not updating...") + return + + camera = objects[0] + + actions = ( camera.animation_data.action, camera.data.animation_data.action ) + + self._remove(objects, lib_container) + + objects_list = self._process( + str(libpath), lib_container, collection.name, actions) + + # Save the list of objects in the metadata container + collection_metadata["objects"] = objects_list + collection_metadata["libpath"] = str(libpath) + collection_metadata["representation"] = str(representation["_id"]) + + bpy.ops.object.select_all(action='DESELECT') + + def remove(self, container: Dict) -> bool: + """Remove an existing container from a Blender scene. + + Arguments: + container (avalon-core:container-1.0): Container to remove, + from `host.ls()`. + + Returns: + bool: Whether the container was deleted. + + Warning: + No nested collections are supported at the moment! + """ + + collection = bpy.data.collections.get( + container["objectName"] + ) + if not collection: + return False + assert not (collection.children), ( + "Nested collections are not supported." + ) + + collection_metadata = collection.get( + blender.pipeline.AVALON_PROPERTY) + objects = collection_metadata["objects"] + lib_container = collection_metadata["lib_container"] + + self._remove(objects, lib_container) + + bpy.data.collections.remove(collection) + + return True diff --git a/pype/plugins/blender/publish/extract_blend.py b/pype/plugins/blender/publish/extract_blend.py index 0924763f12..a5e76dcf4e 100644 --- a/pype/plugins/blender/publish/extract_blend.py +++ b/pype/plugins/blender/publish/extract_blend.py @@ -9,7 +9,7 @@ class ExtractBlend(pype.api.Extractor): label = "Extract Blend" hosts = ["blender"] - families = ["animation", "model", "rig", "action", "layout"] + families = ["model", "camera", "rig", "action", "layout", "animation"] optional = True def process(self, instance): From 85cf9b728e5e5cae6218a12ad2840527a303aaa6 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 Jun 2020 12:56:05 +0100 Subject: [PATCH 06/13] Pep8 compliance --- pype/plugins/blender/load/load_camera.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/plugins/blender/load/load_camera.py b/pype/plugins/blender/load/load_camera.py index a69c01e806..7fd8f94b4e 100644 --- a/pype/plugins/blender/load/load_camera.py +++ b/pype/plugins/blender/load/load_camera.py @@ -60,8 +60,6 @@ class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader): avalon_info = obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) - - if actions[0] is not None: if obj.animation_data is None: obj.animation_data_create() @@ -192,7 +190,7 @@ class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader): camera = objects[0] - actions = ( camera.animation_data.action, camera.data.animation_data.action ) + actions = (camera.animation_data.action, camera.data.animation_data.action) self._remove(objects, lib_container) From f6aeee39be43f24568881707f1a25a64c65c3491 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jun 2020 15:09:18 +0100 Subject: [PATCH 07/13] Subset was not named or validated correctly. --- pype/plugins/photoshop/create/create_image.py | 1 + pype/plugins/photoshop/publish/validate_naming.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pype/plugins/photoshop/create/create_image.py b/pype/plugins/photoshop/create/create_image.py index ff0a5dcb6c..5b2f9f7981 100644 --- a/pype/plugins/photoshop/create/create_image.py +++ b/pype/plugins/photoshop/create/create_image.py @@ -74,4 +74,5 @@ class CreateImage(api.Creator): groups.append(group) for group in groups: + self.data.update({"subset": "image" + group.Name}) photoshop.imprint(group, self.data) diff --git a/pype/plugins/photoshop/publish/validate_naming.py b/pype/plugins/photoshop/publish/validate_naming.py index 1d85ea99a0..51e00da352 100644 --- a/pype/plugins/photoshop/publish/validate_naming.py +++ b/pype/plugins/photoshop/publish/validate_naming.py @@ -1,5 +1,6 @@ import pyblish.api import pype.api +from avalon import photoshop class ValidateNamingRepair(pyblish.api.Action): @@ -22,7 +23,11 @@ class ValidateNamingRepair(pyblish.api.Action): instances = pyblish.api.instances_by_plugin(failed, plugin) for instance in instances: - instance[0].Name = instance.data["name"].replace(" ", "_") + name = instance.data["name"].replace(" ", "_") + instance[0].Name = name + data = photoshop.read(instance[0]) + data["subset"] = "image" + name + photoshop.imprint(instance[0], data) return True @@ -42,3 +47,6 @@ class ValidateNaming(pyblish.api.InstancePlugin): def process(self, instance): msg = "Name \"{}\" is not allowed.".format(instance.data["name"]) assert " " not in instance.data["name"], msg + + msg = "Subset \"{}\" is not allowed.".format(instance.data["subset"]) + assert " " not in instance.data["subset"], msg From 08c7d1e5014a521dd2b3795c26aa07d260981c14 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jun 2020 16:32:39 +0100 Subject: [PATCH 08/13] Fix event server mongo uri - collection name in uri creates a bad database name error - uri was not composed correctly --- pype/modules/ftrack/ftrack_server/lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index 327fab817d..32d31a8bff 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -51,9 +51,8 @@ def get_ftrack_event_mongo_info(): if not _used_ftrack_url or components["database"] is None: components["database"] = database_name - components["collection"] = collection_name - uri = compose_url(components) + uri = compose_url(**components) return uri, components["port"], database_name, collection_name From fc1f8896088bce56df6c806fe0c6a30a5dd5e77e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jun 2020 17:14:28 +0100 Subject: [PATCH 09/13] Ensure collection is not in uri. --- pype/modules/ftrack/ftrack_server/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index 32d31a8bff..9d0f55ada0 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -52,6 +52,8 @@ def get_ftrack_event_mongo_info(): if not _used_ftrack_url or components["database"] is None: components["database"] = database_name + components.pop("collection") + uri = compose_url(**components) return uri, components["port"], database_name, collection_name From 1616d598542e651b86d7f6b5132397d36f7aa0c2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 24 Jun 2020 10:32:57 +0100 Subject: [PATCH 10/13] Fix collection popping. --- pype/modules/ftrack/ftrack_server/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index 9d0f55ada0..8377187ebe 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -52,7 +52,7 @@ def get_ftrack_event_mongo_info(): if not _used_ftrack_url or components["database"] is None: components["database"] = database_name - components.pop("collection") + components.pop("collection", None) uri = compose_url(**components) From 02804710e49c19a4ee96ff0550aa59fe2f504fc2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 11:55:37 +0200 Subject: [PATCH 11/13] moved default maya workspace.mel to ~/pype/resources/maya/ --- {res => pype/resources/maya}/workspace.mel | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {res => pype/resources/maya}/workspace.mel (100%) diff --git a/res/workspace.mel b/pype/resources/maya/workspace.mel similarity index 100% rename from res/workspace.mel rename to pype/resources/maya/workspace.mel From 1fffb6fcf48e6b3b43f9522b4402bf4516392816 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jun 2020 09:36:12 +0100 Subject: [PATCH 12/13] Make sure FFMPEG path is correct. --- pype/plugins/photoshop/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 607e039d14..796e97600c 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -59,7 +59,7 @@ class ExtractReview(pype.api.Extractor): "-vframes", "1", thumbnail_path ] - output = pype.lib._subprocess(args) + output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"]) self.log.debug(output) @@ -79,7 +79,7 @@ class ExtractReview(pype.api.Extractor): "-vframes", "1", mov_path ] - output = pype.lib._subprocess(args) + output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"]) self.log.debug(output) From 3c07d57e1c70f2e8d2fd3219c988d0963fe80264 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jun 2020 09:36:25 +0100 Subject: [PATCH 13/13] Only publish 1 frame for review. --- pype/plugins/photoshop/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 796e97600c..49e932eb67 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -89,7 +89,7 @@ class ExtractReview(pype.api.Extractor): "files": os.path.basename(mov_path), "stagingDir": staging_dir, "frameStart": 1, - "frameEnd": 2, + "frameEnd": 1, "fps": 25, "preview": True, "tags": ["review", "ftrackreview"] @@ -97,7 +97,7 @@ class ExtractReview(pype.api.Extractor): # Required for extract_review plugin (L222 onwards). instance.data["frameStart"] = 1 - instance.data["frameEnd"] = 2 + instance.data["frameEnd"] = 1 instance.data["fps"] = 25 self.log.info(f"Extracted {instance} to {staging_dir}")