From 569cb0a8c34711b2722df9ec65056ac7ef985d49 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 22 Jun 2020 13:26:12 +0100 Subject: [PATCH 01/69] 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 02/69] 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 f6aeee39be43f24568881707f1a25a64c65c3491 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jun 2020 15:09:18 +0100 Subject: [PATCH 03/69] 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 b470642bec3ad6c531764443fd5c9a2f2d9cdb46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 10:10:27 +0200 Subject: [PATCH 04/69] icons in tray at the end of initialization --- pype/tools/tray/pype_tray.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index eec8f61cc4..9b5914ee5e 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -29,11 +29,11 @@ class TrayManager: self.main_window = main_window self.log = Logger().get_logger(self.__class__.__name__) - self.icon_run = QtGui.QIcon(get_resource('circle_green.png')) - self.icon_stay = QtGui.QIcon(get_resource('circle_orange.png')) - self.icon_failed = QtGui.QIcon(get_resource('circle_red.png')) self.services_thread = None + self.icon_run = QtGui.QIcon(get_resource("circle_green.png")) + self.icon_stay = QtGui.QIcon(get_resource("circle_orange.png")) + self.icon_failed = QtGui.QIcon(get_resource("circle_red.png")) def process_presets(self): """Add modules to tray by presets. From 6e9b8d130b20b0a5315de1e883ce96868e74831e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 10:11:08 +0200 Subject: [PATCH 05/69] few class attributes moved to object initialization --- pype/tools/tray/pype_tray.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 9b5914ee5e..55b47f0bad 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -12,11 +12,6 @@ class TrayManager: Load submenus, actions, separators and modules into tray's context. """ - modules = {} - services = {} - services_submenu = None - - errors = [] items = ( config.get_presets(first_run=True) .get('tray', {}) @@ -27,8 +22,14 @@ class TrayManager: def __init__(self, tray_widget, main_window): self.tray_widget = tray_widget self.main_window = main_window + self.log = Logger().get_logger(self.__class__.__name__) + self.modules = {} + self.services = {} + self.services_submenu = None + + self.errors = [] self.services_thread = None self.icon_run = QtGui.QIcon(get_resource("circle_green.png")) From 42615368a3ce3fbaddbd96ba2d79b6874fbe0fb5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 10:12:13 +0200 Subject: [PATCH 06/69] module imports specifications moved to pype --- pype/tools/tray/modules_imports.json | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 pype/tools/tray/modules_imports.json diff --git a/pype/tools/tray/modules_imports.json b/pype/tools/tray/modules_imports.json new file mode 100644 index 0000000000..e9526dcddb --- /dev/null +++ b/pype/tools/tray/modules_imports.json @@ -0,0 +1,58 @@ +[ + { + "title": "User settings", + "type": "module", + "import_path": "pype.modules.user", + "fromlist": ["pype", "modules"] + }, { + "title": "Ftrack", + "type": "module", + "import_path": "pype.modules.ftrack.tray", + "fromlist": ["pype", "modules", "ftrack"] + }, { + "title": "Muster", + "type": "module", + "import_path": "pype.modules.muster", + "fromlist": ["pype", "modules"] + }, { + "title": "Avalon", + "type": "module", + "import_path": "pype.modules.avalon_apps", + "fromlist": ["pype", "modules"] + }, { + "title": "Clockify", + "type": "module", + "import_path": "pype.modules.clockify", + "fromlist": ["pype", "modules"] + }, { + "title": "Standalone Publish", + "type": "module", + "import_path": "pype.modules.standalonepublish", + "fromlist": ["pype", "modules"] + }, { + "title": "Logging", + "type": "module", + "import_path": "pype.modules.logging.tray", + "fromlist": ["pype", "modules", "logging"] + }, { + "title": "Idle Manager", + "type": "module", + "import_path": "pype.modules.idle_manager", + "fromlist": ["pype","modules"] + }, { + "title": "Timers Manager", + "type": "module", + "import_path": "pype.modules.timers_manager", + "fromlist": ["pype","modules"] + }, { + "title": "Rest Api", + "type": "module", + "import_path": "pype.modules.rest_api", + "fromlist": ["pype","modules"] + }, { + "title": "Adobe Communicator", + "type": "module", + "import_path": "pype.modules.adobe_communicator", + "fromlist": ["pype", "modules"] + } +] From d81b0221470d2052cb60202b458ada5f0c89939e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 10:16:13 +0200 Subject: [PATCH 07/69] module specifications are loaded from json in pype --- pype/tools/tray/pype_tray.py | 46 ++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 55b47f0bad..b6dcf98edf 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -31,7 +31,17 @@ class TrayManager: self.errors = [] - self.services_thread = None + CURRENT_DIR = os.path.dirname(__file__) + self.modules_imports = config.load_json( + os.path.join(CURRENT_DIR, "modules_imports.json") + ) + presets = config.get_presets(first_run=True) + try: + self.modules_usage = presets["tray"]["menu_items"]["item_usage"] + except Exception: + self.modules_usage = {} + self.log.critical("Couldn't find modules usage data.") + self.icon_run = QtGui.QIcon(get_resource("circle_green.png")) self.icon_stay = QtGui.QIcon(get_resource("circle_orange.png")) self.icon_failed = QtGui.QIcon(get_resource("circle_red.png")) @@ -62,27 +72,23 @@ class TrayManager: } In this case `Statics Server` won't be used. """ - # Backwards compatible presets loading - if isinstance(self.items, list): - items = self.items - else: - items = [] - # Get booleans is module should be used - usages = self.items.get("item_usage") or {} - for item in self.items.get("item_import", []): - import_path = item.get("import_path") - title = item.get("title") - item_usage = usages.get(title) - if item_usage is None: - item_usage = usages.get(import_path, True) + items = [] + # Get booleans is module should be used + for item in self.modules_imports: + import_path = item.get("import_path") + title = item.get("title") - if item_usage: - items.append(item) - else: - if not title: - title = import_path - self.log.debug("{} - Module ignored".format(title)) + item_usage = self.modules_usage.get(title) + if item_usage is None: + item_usage = self.modules_usage.get(import_path, True) + + if item_usage: + items.append(item) + else: + if not title: + title = import_path + self.log.info("{} - Module ignored".format(title)) if items: self.process_items(items, self.tray_widget.menu) From 166df5c58e8c0098d8e30da64df181b233d56fd1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 10:16:22 +0200 Subject: [PATCH 08/69] cleaned docstring --- pype/tools/tray/pype_tray.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index b6dcf98edf..18cf1e0982 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -57,18 +57,6 @@ class TrayManager: "item_usage": { "Statics Server": false } - }, { - "item_import": [{ - "title": "Ftrack", - "type": "module", - "import_path": "pype.ftrack.tray", - "fromlist": ["pype", "ftrack"] - }, { - "title": "Statics Server", - "type": "module", - "import_path": "pype.services.statics_server", - "fromlist": ["pype","services"] - }] } In this case `Statics Server` won't be used. """ From bf868eb97e9e6399fc4f69cc77cadf2d3272e8c8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 10:16:57 +0200 Subject: [PATCH 09/69] removed items attribute from class definition --- pype/tools/tray/pype_tray.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 18cf1e0982..436734e712 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -12,12 +12,7 @@ class TrayManager: Load submenus, actions, separators and modules into tray's context. """ - items = ( - config.get_presets(first_run=True) - .get('tray', {}) - .get('menu_items', []) - ) - available_sourcetypes = ['python', 'file'] + available_sourcetypes = ["python", "file"] def __init__(self, tray_widget, main_window): self.tray_widget = tray_widget From 02804710e49c19a4ee96ff0550aa59fe2f504fc2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 11:55:37 +0200 Subject: [PATCH 10/69] 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 fd1e9b40abe03b96aec678c1dbe88ef0d22592c3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 24 Jun 2020 12:16:44 +0100 Subject: [PATCH 11/69] Flag outdated containers on startup and publish. --- pype/hosts/photoshop/__init__.py | 43 ++++++++++++++++++- .../global/publish/validate_containers.py | 2 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/pype/hosts/photoshop/__init__.py b/pype/hosts/photoshop/__init__.py index 01ed757a8d..564e9c8a05 100644 --- a/pype/hosts/photoshop/__init__.py +++ b/pype/hosts/photoshop/__init__.py @@ -1,9 +1,48 @@ import os +import sys -from avalon import api +from avalon import api, io +from avalon.vendor import Qt +from pype import lib import pyblish.api +def check_inventory(): + if not lib.any_outdated(): + return + + host = 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) + + # 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(): + check_inventory() + + def install(): print("Installing Pype config...") @@ -27,6 +66,8 @@ def install(): "instanceToggled", on_pyblish_instance_toggled ) + api.on("application.launched", application_launch) + def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle layer visibility on instance toggles.""" diff --git a/pype/plugins/global/publish/validate_containers.py b/pype/plugins/global/publish/validate_containers.py index 44cb5def3c..9a07770aed 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", "photoshop"] optional = True actions = [ShowInventory] From 5a7846a9fe1e7b13940a71c9a7336e7fa948ef9f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 24 Jun 2020 13:52:14 +0100 Subject: [PATCH 12/69] Only zip files are supported. --- pype/plugins/harmony/load/load_template_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/harmony/load/load_template_workfile.py b/pype/plugins/harmony/load/load_template_workfile.py index a9dcd0c776..b727cf865c 100644 --- a/pype/plugins/harmony/load/load_template_workfile.py +++ b/pype/plugins/harmony/load/load_template_workfile.py @@ -40,5 +40,5 @@ class ImportWorkfileLoader(ImportTemplateLoader): """Import workfiles.""" families = ["workfile"] - representations = ["*"] + representations = ["zip"] label = "Import Workfile" From a2d511a27554345aa98dcb8568a43a91be89dd90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 15:00:57 +0200 Subject: [PATCH 13/69] moved tray icons in subfolder icons --- pype/resources/icons/circle_green.png | Bin 0 -> 35899 bytes pype/resources/icons/circle_orange.png | Bin 0 -> 37564 bytes pype/resources/icons/circle_red.png | Bin 0 -> 36200 bytes .../resources/{icon.png => icons/pype_icon.png} | Bin .../{icon_dev.png => icons/pype_icon_dev.png} | Bin .../{splash.png => icons/pype_splash.png} | Bin .../pype_splash_dev.png} | Bin pype/resources/{ => icons}/working.svg | 0 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 pype/resources/icons/circle_green.png create mode 100644 pype/resources/icons/circle_orange.png create mode 100644 pype/resources/icons/circle_red.png rename pype/resources/{icon.png => icons/pype_icon.png} (100%) rename pype/resources/{icon_dev.png => icons/pype_icon_dev.png} (100%) rename pype/resources/{splash.png => icons/pype_splash.png} (100%) rename pype/resources/{splash_dev.png => icons/pype_splash_dev.png} (100%) rename pype/resources/{ => icons}/working.svg (100%) diff --git a/pype/resources/icons/circle_green.png b/pype/resources/icons/circle_green.png new file mode 100644 index 0000000000000000000000000000000000000000..b83369a9e341520d4eb8481ee501b2874f2ce356 GIT binary patch literal 35899 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 literal 0 HcmV?d00001 diff --git a/pype/resources/icons/circle_orange.png b/pype/resources/icons/circle_orange.png new file mode 100644 index 0000000000000000000000000000000000000000..656f318e0c9b4eadc4f6f54def39ab4e01521f5e GIT binary patch literal 37564 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 Date: Wed, 24 Jun 2020 15:01:44 +0200 Subject: [PATCH 14/69] added functions for getting pype icon and pype splash into resources --- pype/resources/__init__.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pype/resources/__init__.py b/pype/resources/__init__.py index 248614ae9d..ba882a84fb 100644 --- a/pype/resources/__init__.py +++ b/pype/resources/__init__.py @@ -14,3 +14,25 @@ def get_resource(*args): *args ) ) + + +def pype_icon_filepath(debug=None): + if debug is None: + debug = bool(os.getenv("PYPE_DEV")) + + if debug: + icon_file_name = "pype_icon_dev.png" + else: + icon_file_name = "pype_icon.png" + return get_resource("icons", icon_file_name) + + +def pype_splash_filepath(debug=None): + if debug is None: + debug = bool(os.getenv("PYPE_DEV")) + + if debug: + splash_file_name = "pype_splash_dev.png" + else: + splash_file_name = "pype_splash.png" + return get_resource("icons", splash_file_name) From c7bcac6b4660493d6c45dc9fb9798314ebe83354 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 15:05:22 +0200 Subject: [PATCH 15/69] used new resource way of getting pype icon in code --- pype/modules/clockify/widget_message.py | 5 ++-- pype/modules/clockify/widget_settings.py | 6 ++--- pype/modules/ftrack/tray/login_dialog.py | 6 ++--- pype/modules/muster/widget_login.py | 6 ++--- pype/modules/user/widget_user.py | 4 ++-- pype/tools/tray/pype_tray.py | 30 ++++++++++-------------- 6 files changed, 24 insertions(+), 33 deletions(-) diff --git a/pype/modules/clockify/widget_message.py b/pype/modules/clockify/widget_message.py index f919c3f819..9e4fad1df1 100644 --- a/pype/modules/clockify/widget_message.py +++ b/pype/modules/clockify/widget_message.py @@ -1,5 +1,6 @@ from Qt import QtCore, QtGui, QtWidgets from avalon import style +from pype.api import resources class MessageWidget(QtWidgets.QWidget): @@ -19,8 +20,8 @@ class MessageWidget(QtWidgets.QWidget): if parent and hasattr(parent, 'icon'): self.setWindowIcon(parent.icon) else: - from pypeapp.resources import get_resource - self.setWindowIcon(QtGui.QIcon(get_resource('icon.png'))) + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) self.setWindowFlags( QtCore.Qt.WindowCloseButtonHint | diff --git a/pype/modules/clockify/widget_settings.py b/pype/modules/clockify/widget_settings.py index 956bdb1916..7e5ee300bb 100644 --- a/pype/modules/clockify/widget_settings.py +++ b/pype/modules/clockify/widget_settings.py @@ -1,6 +1,7 @@ import os from Qt import QtCore, QtGui, QtWidgets from avalon import style +from pype.api import resources class ClockifySettings(QtWidgets.QWidget): @@ -26,10 +27,7 @@ class ClockifySettings(QtWidgets.QWidget): elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(self.parent.parent.icon) else: - pype_setup = os.getenv('PYPE_SETUP_PATH') - items = [pype_setup, "app", "resources", "icon.png"] - fname = os.path.sep.join(items) - icon = QtGui.QIcon(fname) + icon = QtGui.QIcon(resources.pype_icon_filepath()) self.setWindowIcon(icon) self.setWindowFlags( diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index 3b8a366209..e0614513a3 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -3,6 +3,7 @@ import requests from avalon import style from pype.modules.ftrack import credentials from . import login_tools +from pype.api import resources from Qt import QtCore, QtGui, QtWidgets @@ -29,10 +30,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(self.parent.parent.icon) else: - pype_setup = os.getenv('PYPE_SETUP_PATH') - items = [pype_setup, "app", "resources", "icon.png"] - fname = os.path.sep.join(items) - icon = QtGui.QIcon(fname) + icon = QtGui.QIcon(resources.pype_icon_filepath()) self.setWindowIcon(icon) self.setWindowFlags( diff --git a/pype/modules/muster/widget_login.py b/pype/modules/muster/widget_login.py index 8de0d3136a..f446c13325 100644 --- a/pype/modules/muster/widget_login.py +++ b/pype/modules/muster/widget_login.py @@ -1,6 +1,7 @@ import os from Qt import QtCore, QtGui, QtWidgets from avalon import style +from pype.api import resources class MusterLogin(QtWidgets.QWidget): @@ -23,10 +24,7 @@ class MusterLogin(QtWidgets.QWidget): elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(parent.parent.icon) else: - pype_setup = os.getenv('PYPE_SETUP_PATH') - items = [pype_setup, "app", "resources", "icon.png"] - fname = os.path.sep.join(items) - icon = QtGui.QIcon(fname) + icon = QtGui.QIcon(resources.pype_icon_filepath()) self.setWindowIcon(icon) self.setWindowFlags( diff --git a/pype/modules/user/widget_user.py b/pype/modules/user/widget_user.py index 1d43941345..ba211c4737 100644 --- a/pype/modules/user/widget_user.py +++ b/pype/modules/user/widget_user.py @@ -1,6 +1,6 @@ from Qt import QtCore, QtGui, QtWidgets -from pype.resources import get_resource from avalon import style +from pype.api import resources class UserWidget(QtWidgets.QWidget): @@ -14,7 +14,7 @@ class UserWidget(QtWidgets.QWidget): self.module = module # Style - icon = QtGui.QIcon(get_resource("icon.png")) + icon = QtGui.QIcon(resources.pype_icon_filepath()) self.setWindowIcon(icon) self.setWindowTitle("Username Settings") self.setMinimumWidth(self.MIN_WIDTH) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index eec8f61cc4..3c5e1fb612 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -3,8 +3,7 @@ import sys import platform from avalon import style from Qt import QtCore, QtGui, QtWidgets, QtSvg -from pype.resources import get_resource -from pype.api import config, Logger +from pype.api import config, Logger, resources class TrayManager: @@ -29,9 +28,15 @@ class TrayManager: self.main_window = main_window self.log = Logger().get_logger(self.__class__.__name__) - self.icon_run = QtGui.QIcon(get_resource('circle_green.png')) - self.icon_stay = QtGui.QIcon(get_resource('circle_orange.png')) - self.icon_failed = QtGui.QIcon(get_resource('circle_red.png')) + self.icon_run = QtGui.QIcon( + resources.get_resource("icons", "circle_green.png") + ) + self.icon_stay = QtGui.QIcon( + resources.get_resource("icons", "circle_orange.png") + ) + self.icon_failed = QtGui.QIcon( + resources.get_resource("icons", "circle_red.png") + ) self.services_thread = None @@ -333,12 +338,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): :type parent: QtWidgets.QMainWindow """ def __init__(self, parent): - if os.getenv("PYPE_DEV"): - icon_file_name = "icon_dev.png" - else: - icon_file_name = "icon.png" - - self.icon = QtGui.QIcon(get_resource(icon_file_name)) + self.icon = QtGui.QIcon(resources.pype_icon_filepath()) QtWidgets.QSystemTrayIcon.__init__(self, self.icon, parent) @@ -402,7 +402,7 @@ class TrayMainWindow(QtWidgets.QMainWindow): self.trayIcon.show() def set_working_widget(self): - image_file = get_resource('working.svg') + image_file = resources.get_resource("icons", "working.svg") img_pix = QtGui.QPixmap(image_file) if image_file.endswith('.svg'): widget = QtSvg.QSvgWidget(image_file) @@ -492,11 +492,7 @@ class PypeTrayApplication(QtWidgets.QApplication): splash_widget.hide() def set_splash(self): - if os.getenv("PYPE_DEV"): - splash_file_name = "splash_dev.png" - else: - splash_file_name = "splash.png" - splash_pix = QtGui.QPixmap(get_resource(splash_file_name)) + splash_pix = QtGui.QPixmap(resources.pype_splash_filepath()) splash = QtWidgets.QSplashScreen(splash_pix) splash.setMask(splash_pix.mask()) splash.setEnabled(False) From 38c919e8f81e3afc16d40a944179fc434d52e0fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 15:05:30 +0200 Subject: [PATCH 16/69] added resources to api --- pype/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/api.py b/pype/api.py index 5775bb3ce4..44a31f2626 100644 --- a/pype/api.py +++ b/pype/api.py @@ -12,6 +12,8 @@ from pypeapp.lib.mongo import ( get_default_components ) +from . import resources + from .plugin import ( Extractor, @@ -54,6 +56,8 @@ __all__ = [ "compose_url", "get_default_components", + # Resources + "resources", # plugin classes "Extractor", # ordering From a3b78d7e1d7cf569514d6a91e72bbc752b3f3cfc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 15:05:55 +0200 Subject: [PATCH 17/69] removed forgotten images already moved to subfolder --- pype/resources/circle_green.png | Bin 35899 -> 0 bytes pype/resources/circle_orange.png | Bin 37564 -> 0 bytes pype/resources/circle_red.png | Bin 36200 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pype/resources/circle_green.png delete mode 100644 pype/resources/circle_orange.png delete mode 100644 pype/resources/circle_red.png diff --git a/pype/resources/circle_green.png b/pype/resources/circle_green.png deleted file mode 100644 index b83369a9e341520d4eb8481ee501b2874f2ce356..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35899 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 diff --git a/pype/resources/circle_orange.png b/pype/resources/circle_orange.png deleted file mode 100644 index 656f318e0c9b4eadc4f6f54def39ab4e01521f5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37564 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 Date: Wed, 24 Jun 2020 15:54:38 +0200 Subject: [PATCH 18/69] move message widget to center of screen --- pype/widgets/message_window.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/widgets/message_window.py b/pype/widgets/message_window.py index 3532d2df44..41c709b933 100644 --- a/pype/widgets/message_window.py +++ b/pype/widgets/message_window.py @@ -52,6 +52,19 @@ def message(title=None, message=None, level="info", parent=None): app = parent if not app: app = QtWidgets.QApplication(sys.argv) + ex = Window(app, title, message, level) ex.show() + + # Move widget to center of screen + try: + desktop_rect = QtWidgets.QApplication.desktop().availableGeometry(ex) + center = desktop_rect.center() + ex.move( + center.x() - (ex.width() * 0.5), + center.y() - (ex.height() * 0.5) + ) + except Exception: + # skip all possible issues that may happen feature is not crutial + log.warning("Couldn't center message.", exc_info=True) # sys.exit(app.exec_()) From b1ee15b0b52904759458de4762e8483451f52948 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 24 Jun 2020 16:26:35 +0100 Subject: [PATCH 19/69] Model support for namespaces --- pype/hosts/blender/plugin.py | 22 ++++++++- pype/plugins/blender/load/load_model.py | 65 +++++++++++++------------ 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index 77fce90d65..5a2596c9d8 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -5,7 +5,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from avalon import api, blender VALID_EXTENSIONS = [".blend"] @@ -20,6 +20,26 @@ def asset_name( return name +def asset_namespace( + asset: str, subset: str +) -> str: + """Return a unique namespace based on the asset name.""" + avalon_containers = bpy.data.collections.get( + blender.pipeline.AVALON_CONTAINERS + ) + if avalon_containers is None: + return "1" + collections_names = [ + c.name for c in avalon_containers.children + ] + count = 1 + name = f"{asset_name(asset, subset, str(count))}_CON" + while name in collections_names: + count += 1 + name = f"{asset_name(asset, subset, str(count))}_CON" + return str(count) + + def create_blender_context(active: Optional[bpy.types.Object] = None, selected: Optional[bpy.types.Object] = None,): """Create a new Blender context. If an object is passed as diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 4a8f43cd48..b16f5a40b1 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -7,12 +7,12 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.plugin +import pype.hosts.blender.plugin as plugin logger = logging.getLogger("pype").getChild("blender").getChild("load_model") -class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): +class BlendModelLoader(plugin.AssetLoader): """Load models from a .blend file. Because they come from a .blend file we can simply link the collection that @@ -30,16 +30,20 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - + def _remove(self, objects, container): for obj in objects: - + for material_slot in obj.material_slots: + bpy.data.materials.remove(material_slot.material) bpy.data.meshes.remove(obj.data) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + bpy.data.collections.remove(container) + + def prepare_data(self, data, container_name): + name = data.name + data = data.make_local() + data.name = f"{name}:{container_name}" def _process(self, libpath, lib_container, container_name): - relative = bpy.context.preferences.filepaths.use_relative_paths with bpy.data.libraries.load( libpath, link=True, relative=relative @@ -51,33 +55,26 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) model_container = scene.collection.children[lib_container].make_local() - - objects_list = [] + model_container.name = container_name for obj in model_container.objects: - - obj = obj.make_local() - - obj.data.make_local() + self.prepare_data(obj, container_name) + self.prepare_data(obj.data, container_name) for material_slot in obj.material_slots: - - material_slot.material.make_local() + self.prepare_data(material_slot.material, container_name) 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}) - objects_list.append(obj) - model_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return model_container def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -94,13 +91,18 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): 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( + + lib_container = plugin.asset_name( + asset, subset + ) + namespace = namespace or plugin.asset_namespace( + asset, subset + ) + container_name = plugin.asset_name( asset, subset, namespace ) collection = bpy.data.collections.new(lib_container) - collection.name = container_name blender.pipeline.containerise_existing( collection, name, @@ -115,11 +117,13 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process( + obj_container = self._process( libpath, lib_container, container_name) + container_metadata["obj_container"] = obj_container + # Save the list of objects in the metadata container - container_metadata["objects"] = objects_list + container_metadata["objects"] = obj_container.all_objects nodes = list(collection.objects) nodes.append(collection) @@ -162,7 +166,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -171,6 +175,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) @@ -187,7 +192,7 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): logger.info("Library already loaded, not updating...") return - self._remove(objects, lib_container) + self._remove(objects, obj_container) objects_list = self._process( str(libpath), lib_container, collection.name) @@ -222,16 +227,16 @@ class BlendModelLoader(pype.hosts.blender.plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] - self._remove(objects, lib_container) + self._remove(objects, obj_container) bpy.data.collections.remove(collection) return True -class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader): +class CacheModelLoader(plugin.AssetLoader): """Load cache models. Stores the imported asset in a collection named after the asset. @@ -267,7 +272,7 @@ class CacheModelLoader(pype.hosts.blender.plugin.AssetLoader): subset = context["subset"]["name"] # TODO (jasper): evaluate use of namespace which is 'alien' to Blender. lib_container = container_name = ( - pype.hosts.blender.plugin.asset_name(asset, subset, namespace) + plugin.asset_name(asset, subset, namespace) ) relative = bpy.context.preferences.filepaths.use_relative_paths From c558f46dd3dc165577d02943e8988926cfa18b58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 17:27:05 +0200 Subject: [PATCH 20/69] extensions moved from presets to standalone publisher widgets --- .../widgets/widget_drop_frame.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/modules/standalonepublish/widgets/widget_drop_frame.py index 80e67aa69a..76904af0cb 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_frame.py @@ -10,6 +10,34 @@ from . import DropEmpty, ComponentsList, ComponentItem class DropDataFrame(QtWidgets.QFrame): + image_extensions = [ + ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal", + ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits", + ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer", + ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", + ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", + ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", + ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", + ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras", + ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", + ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", + ".xpm", ".xwd" + ] + video_extensions = [ + ".3g2", ".3gp", ".amv", ".asf", ".avi", ".drc", ".f4a", ".f4b", + ".f4p", ".f4v", ".flv", ".gif", ".gifv", ".m2v", ".m4p", ".m4v", + ".mkv", ".mng", ".mov", ".mp2", ".mp4", ".mpe", ".mpeg", ".mpg", + ".mpv", ".mxf", ".nsv", ".ogg", ".ogv", ".qt", ".rm", ".rmvb", + ".roq", ".svi", ".vob", ".webm", ".wmv", ".yuv" + ] + extensions = { + "nuke": [".nk"], + "maya": [".ma", ".mb"], + "houdini": [".hip"], + "image_file": image_extensions, + "video_file": video_extensions + } + def __init__(self, parent): super().__init__() self.parent_widget = parent From 9859a0032d06399dae79a8a417b45892edb237c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 24 Jun 2020 17:27:26 +0200 Subject: [PATCH 21/69] skip using presets attribute --- .../widgets/widget_drop_frame.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/modules/standalonepublish/widgets/widget_drop_frame.py index 76904af0cb..c91e906f45 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_frame.py @@ -41,7 +41,6 @@ class DropDataFrame(QtWidgets.QFrame): def __init__(self, parent): super().__init__() self.parent_widget = parent - self.presets = config.get_presets()['standalone_publish'] self.setAcceptDrops(True) layout = QtWidgets.QVBoxLayout(self) @@ -54,7 +53,9 @@ class DropDataFrame(QtWidgets.QFrame): QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.drop_widget.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth( + self.drop_widget.sizePolicy().hasHeightForWidth() + ) self.drop_widget.setSizePolicy(sizePolicy) layout.addWidget(self.drop_widget) @@ -283,8 +284,8 @@ class DropDataFrame(QtWidgets.QFrame): file_info = data['file_info'] if ( - ext in self.presets['extensions']['image_file'] or - ext in self.presets['extensions']['video_file'] + ext in self.image_extensions + or ext in self.video_extensions ): probe_data = self.load_data_with_probe(filepath) if 'fps' not in data: @@ -321,7 +322,7 @@ class DropDataFrame(QtWidgets.QFrame): data[key] = value icon = 'default' - for ico, exts in self.presets['extensions'].items(): + for ico, exts in self.extensions.items(): if ext in exts: icon = ico break @@ -332,17 +333,16 @@ class DropDataFrame(QtWidgets.QFrame): icon += 's' data['icon'] = icon data['thumb'] = ( - ext in self.presets['extensions']['image_file'] or - ext in self.presets['extensions']['video_file'] + ext in self.image_extensions + or ext in self.video_extensions ) data['prev'] = ( - ext in self.presets['extensions']['video_file'] or - (new_is_seq and ext in self.presets['extensions']['image_file']) + ext in self.video_extensions + or (new_is_seq and ext in self.image_extensions) ) actions = [] - found = False for item in self.components_list.widgets(): if data['ext'] != item.in_data['ext']: From a0096d6cdf062c81fb1c4c9dc13e27aa49da73f6 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 25 Jun 2020 11:14:06 +0100 Subject: [PATCH 22/69] Rig support for namespaces --- pype/plugins/blender/load/load_model.py | 21 ++++----- pype/plugins/blender/load/load_rig.py | 60 +++++++++++++------------ 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index b16f5a40b1..6f34b40e70 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -17,10 +17,6 @@ class BlendModelLoader(plugin.AssetLoader): Because they come from a .blend file we can simply link the collection that contains the model. There is no further need to 'containerise' it. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. """ families = ["model"] @@ -102,16 +98,16 @@ class BlendModelLoader(plugin.AssetLoader): asset, subset, namespace ) - collection = bpy.data.collections.new(lib_container) + container = bpy.data.collections.new(lib_container) blender.pipeline.containerise_existing( - collection, + container, name, namespace, context, self.__class__.__name__, ) - container_metadata = collection.get( + container_metadata = container.get( blender.pipeline.AVALON_PROPERTY) container_metadata["libpath"] = libpath @@ -125,8 +121,8 @@ class BlendModelLoader(plugin.AssetLoader): # Save the list of objects in the metadata container container_metadata["objects"] = obj_container.all_objects - nodes = list(collection.objects) - nodes.append(collection) + nodes = list(container.objects) + nodes.append(container) self[:] = nodes return nodes @@ -148,7 +144,7 @@ class BlendModelLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.debug( + logger.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -194,11 +190,12 @@ class BlendModelLoader(plugin.AssetLoader): self._remove(objects, obj_container) - objects_list = self._process( + obj_container = self._process( str(libpath), lib_container, collection.name) # Save the list of objects in the metadata container - collection_metadata["objects"] = objects_list + collection_metadata["obj_container"] = obj_container + collection_metadata["objects"] = obj_container.all_objects collection_metadata["libpath"] = str(libpath) collection_metadata["representation"] = str(representation["_id"]) diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 3e53ff0363..41343e9c3a 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -7,20 +7,16 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.plugin +import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_model") +logger = logging.getLogger("pype").getChild("blender").getChild("load_rig") -class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): +class BlendRigLoader(plugin.AssetLoader): """Load rigs from a .blend file. Because they come from a .blend file we can simply link the collection that contains the model. There is no further need to 'containerise' it. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. """ families = ["rig"] @@ -33,7 +29,6 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): def _remove(self, objects, lib_container): for obj in objects: - if obj.type == 'ARMATURE': bpy.data.armatures.remove(obj.data) elif obj.type == 'MESH': @@ -44,8 +39,12 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): bpy.data.collections.remove(bpy.data.collections[lib_container]) - def _process(self, libpath, lib_container, container_name, action): + def prepare_data(self, data, container_name): + name = data.name + data = data.make_local() + data.name = f"{name}:{container_name}" + def _process(self, libpath, lib_container, container_name, action): relative = bpy.context.preferences.filepaths.use_relative_paths with bpy.data.libraries.load( libpath, link=True, relative=relative @@ -57,6 +56,7 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) rig_container = scene.collection.children[lib_container].make_local() + rig_container.name = container_name meshes = [] armatures = [ @@ -65,15 +65,15 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): objects_list = [] for child in rig_container.children: - child.make_local() + self.prepare_data(child, container_name) meshes.extend( child.objects ) # Link meshes first, then armatures. # The armature is unparented for all the non-local meshes, # when it is made local. for obj in meshes + armatures: - obj = obj.make_local() - obj.data.make_local() + self.prepare_data(obj, container_name) + self.prepare_data(obj.data, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -84,13 +84,11 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): if obj.type == 'ARMATURE' and action is not None: obj.animation_data.action = action - objects_list.append(obj) - rig_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return rig_container def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -107,13 +105,17 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): 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( + lib_container = plugin.asset_name( + asset, subset + ) + namespace = namespace or plugin.asset_namespace( + asset, subset + ) + container_name = plugin.asset_name( asset, subset, namespace ) container = bpy.data.collections.new(lib_container) - container.name = container_name blender.pipeline.containerise_existing( container, name, @@ -128,11 +130,13 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process( + obj_container = self._process( libpath, lib_container, container_name, None) + container_metadata["obj_container"] = obj_container + # Save the list of objects in the metadata container - container_metadata["objects"] = objects_list + container_metadata["objects"] = obj_container.all_objects nodes = list(container.objects) nodes.append(container) @@ -151,11 +155,9 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): 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() @@ -177,7 +179,7 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, ( + assert extension in plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -186,6 +188,7 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) @@ -208,13 +211,14 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): action = armatures[0].animation_data.action - self._remove(objects, lib_container) + self._remove(objects, obj_container) - objects_list = self._process( + obj_container = self._process( str(libpath), lib_container, collection.name, action) # Save the list of objects in the metadata container - collection_metadata["objects"] = objects_list + collection_metadata["obj_container"] = obj_container + collection_metadata["objects"] = obj_container.all_objects collection_metadata["libpath"] = str(libpath) collection_metadata["representation"] = str(representation["_id"]) @@ -246,9 +250,9 @@ class BlendRigLoader(pype.hosts.blender.plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] - self._remove(objects, lib_container) + self._remove(objects, obj_container) bpy.data.collections.remove(collection) From 7f30c245b957c947cfe2c9b6b0816e511df99572 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 25 Jun 2020 17:22:09 +0200 Subject: [PATCH 23/69] use HOST_WORKFILE_EXTENSIONS variable from avalon for nukestudio extensions --- pype/hosts/nukestudio/workio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/hosts/nukestudio/workio.py b/pype/hosts/nukestudio/workio.py index eee6654a4c..2cf898aa33 100644 --- a/pype/hosts/nukestudio/workio.py +++ b/pype/hosts/nukestudio/workio.py @@ -6,8 +6,9 @@ from pype.api import Logger log = Logger().get_logger(__name__, "nukestudio") + def file_extensions(): - return [".hrox"] + return api.HOST_WORKFILE_EXTENSIONS["nukestudio"] def has_unsaved_changes(): From f19ffc13e619302f389cf6166d2a12aa68eca83d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 25 Jun 2020 17:22:57 +0200 Subject: [PATCH 24/69] ftrack app launcher sets AVALON_LAST_WORKFILE variable where path to last workfile is stored --- pype/modules/ftrack/lib/ftrack_app_handler.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 00bd13fd73..df6420a5f7 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -4,9 +4,11 @@ import copy import platform import avalon.lib import acre +import getpass from pype import lib as pypelib from pype.api import config, Anatomy from .ftrack_action_handler import BaseAction +from avalon.api import last_workfile, HOST_WORKFILE_EXTENSIONS class AppAction(BaseAction): @@ -152,10 +154,11 @@ class AppAction(BaseAction): hierarchy = "" asset_doc_parents = asset_document["data"].get("parents") - if len(asset_doc_parents) > 0: + if asset_doc_parents: hierarchy = os.path.join(*asset_doc_parents) application = avalon.lib.get_application(self.identifier) + host_name = application["application_dir"] data = { "project": { "name": entity["project"]["full_name"], @@ -163,7 +166,7 @@ class AppAction(BaseAction): }, "task": entity["name"], "asset": asset_name, - "app": application["application_dir"], + "app": host_name, "hierarchy": hierarchy } @@ -187,6 +190,21 @@ class AppAction(BaseAction): except FileExistsError: pass + last_workfile_path = None + extensions = HOST_WORKFILE_EXTENSIONS.get(host_name) + if extensions: + # Find last workfile + file_template = anatomy.templates["work"]["file"] + data.update({ + "version": 1, + "user": getpass.getuser(), + "ext": extensions[0] + }) + + last_workfile_path = last_workfile( + workdir, file_template, data, extensions, True + ) + # set environments for Avalon prep_env = copy.deepcopy(os.environ) prep_env.update({ @@ -198,6 +216,8 @@ class AppAction(BaseAction): "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir }) + if last_workfile_path: + prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path prep_env.update(anatomy.roots_obj.root_environments()) # collect all parents from the task From 7ca6e8f751f16f6005f444060b2a2108f4e5a662 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:52:46 +0200 Subject: [PATCH 25/69] fix setData return type in model items --- pype/tools/pyblish_pype/model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index 203b512d12..9086003258 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -319,7 +319,7 @@ class PluginItem(QtGui.QStandardItem): return False self.plugin.active = value self.emitDataChanged() - return True + return elif role == Roles.PluginActionProgressRole: if isinstance(value, list): @@ -652,14 +652,14 @@ class InstanceItem(QtGui.QStandardItem): def setData(self, value, role=(QtCore.Qt.UserRole + 1)): if role == QtCore.Qt.CheckStateRole: if not self.data(Roles.IsEnabledRole): - return False + return self.instance.data["publish"] = value self.emitDataChanged() - return True + return if role == Roles.IsEnabledRole: if not self.instance.optional: - return False + return if role == Roles.PublishFlagsRole: if isinstance(value, list): @@ -692,12 +692,12 @@ class InstanceItem(QtGui.QStandardItem): self.instance._publish_states = value self.emitDataChanged() - return True + return if role == Roles.LogRecordsRole: self.instance._logs = value self.emitDataChanged() - return True + return return super(InstanceItem, self).setData(value, role) From c770d1f7eeb7b164d78bc498fe69af735edb717f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:53:31 +0200 Subject: [PATCH 26/69] PluginDelegate stays same as OverviewGroupSection --- pype/tools/pyblish_pype/delegate.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pype/tools/pyblish_pype/delegate.py b/pype/tools/pyblish_pype/delegate.py index e88835b81a..11e12e0c5a 100644 --- a/pype/tools/pyblish_pype/delegate.py +++ b/pype/tools/pyblish_pype/delegate.py @@ -279,14 +279,14 @@ class InstanceItemDelegate(QtWidgets.QStyledItemDelegate): return QtCore.QSize(option.rect.width(), 20) -class OverviewGroupSection(QtWidgets.QStyledItemDelegate): - """Generic delegate for section header""" +class PluginDelegate(QtWidgets.QStyledItemDelegate): + """Generic delegate for plugin header""" item_class = None def __init__(self, parent): - super(OverviewGroupSection, self).__init__(parent) - self.item_delegate = self.item_class(parent) + super(PluginDelegate, self).__init__(parent) + self.item_delegate = PluginItemDelegate(parent) def paint(self, painter, option, index): if index.data(Roles.TypeRole) in ( @@ -343,7 +343,7 @@ class OverviewGroupSection(QtWidgets.QStyledItemDelegate): painter.setFont(fonts["awesome6"]) painter.setPen(QtGui.QPen(colors["idle"])) - painter.drawText(expander_rect, expander_icon) + painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon) # Draw label painter.setFont(fonts["h5"]) @@ -362,11 +362,6 @@ class OverviewGroupSection(QtWidgets.QStyledItemDelegate): return QtCore.QSize(option.rect.width(), 20) -class PluginDelegate(OverviewGroupSection): - """Generic delegate for model items in proxy tree view""" - item_class = PluginItemDelegate - - class InstanceDelegate(OverviewGroupSection): """Generic delegate for model items in proxy tree view""" item_class = InstanceItemDelegate From 3e5a49cd73a54b4dacb9ec49819df58bbe389034 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:53:59 +0200 Subject: [PATCH 27/69] InstanceDelegate can paint 2 parts, expand button and label itself --- pype/tools/pyblish_pype/delegate.py | 138 +++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 5 deletions(-) diff --git a/pype/tools/pyblish_pype/delegate.py b/pype/tools/pyblish_pype/delegate.py index 11e12e0c5a..2f1e9266ea 100644 --- a/pype/tools/pyblish_pype/delegate.py +++ b/pype/tools/pyblish_pype/delegate.py @@ -279,6 +279,139 @@ class InstanceItemDelegate(QtWidgets.QStyledItemDelegate): return QtCore.QSize(option.rect.width(), 20) +class InstanceDelegate(QtWidgets.QStyledItemDelegate): + """Generic delegate for instance header""" + + def __init__(self, parent): + super(InstanceDelegate, self).__init__(parent) + self.item_delegate = InstanceItemDelegate(parent) + + def paint(self, painter, option, index): + if index.data(Roles.TypeRole) in ( + model.InstanceType, model.PluginType + ): + self.item_delegate.paint(painter, option, index) + return + + self.group_item_paint(painter, option, index) + + def group_item_paint(self, painter, option, index): + """Paint text + _ + My label + """ + body_rect = QtCore.QRectF(option.rect) + bg_rect = QtCore.QRectF( + body_rect.left(), body_rect.top() + 1, + body_rect.width() - 5, body_rect.height() - 2 + ) + + expander_rect = QtCore.QRectF(bg_rect) + expander_rect.setWidth(EXPANDER_WIDTH) + + remainder_rect = QtCore.QRectF( + expander_rect.x() + expander_rect.width(), + expander_rect.y(), + bg_rect.width() - expander_rect.width(), + expander_rect.height() + ) + + radius = 8.0 + width = float(expander_rect.width()) + height = float(expander_rect.height()) + x_pos = expander_rect.x() + y_pos = expander_rect.y() + + expander_path = QtGui.QPainterPath() + expander_path.moveTo(x_pos + width - radius, y_pos) + expander_path.lineTo(x_pos + width, y_pos) + expander_path.lineTo(x_pos + width, y_pos + height) + expander_path.lineTo(x_pos + width - radius, y_pos + height) + expander_path.arcTo(x_pos, y_pos, radius, height, 270.0, -180.0) + expander_path.closeSubpath() + + width = float(remainder_rect.width()) + height = float(remainder_rect.height()) + x_pos = remainder_rect.x() + y_pos = remainder_rect.y() + + remainder_path = QtGui.QPainterPath() + remainder_path.moveTo(x_pos, y_pos) + remainder_path.lineTo(x_pos + width - radius, y_pos) + remainder_path.arcTo( + x_pos + width - radius, y_pos, + radius, height, + 90.0, -180.0 + ) + remainder_path.lineTo(x_pos, y_pos + height) + remainder_path.lineTo(x_pos, y_pos) + remainder_path.closeSubpath() + + mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) + painted_expander = False + painted_remainder = False + if option.state & QtWidgets.QStyle.State_Selected: + if expander_rect.contains(mouse_pos): + painter.fillPath(expander_path, colors["expander-selected"]) + painted_expander = True + else: + painter.fillPath(remainder_path, colors["group-selected"]) + painted_remainder = True + + elif option.state & QtWidgets.QStyle.State_MouseOver: + if expander_rect.contains(mouse_pos): + painter.fillPath(expander_path, colors["expander-hover"]) + painted_expander = True + else: + painter.fillPath(remainder_path, colors["group-hover"]) + painted_remainder = True + + if not painted_expander: + painter.fillPath(expander_path, colors["expander-bg"]) + + if not painted_remainder: + painter.fillPath(remainder_path, colors["group"]) + + text_height = font_metrics["awesome6"].height() + adjust_value = (expander_rect.height() - text_height) / 2 + expander_rect.adjust( + adjust_value + 1.5, adjust_value - 0.5, + -adjust_value + 1.5, -adjust_value - 0.5 + ) + + offset = (remainder_rect.height() - font_metrics["h5"].height()) / 2 + label_rect = QtCore.QRectF(remainder_rect.adjusted( + 5, offset - 1, 0, 0 + )) + + expander_icon = icons["plus-sign"] + + expanded = self.parent().isExpanded(index) + if expanded: + expander_icon = icons["minus-sign"] + label = index.data(QtCore.Qt.DisplayRole) + label = font_metrics["h5"].elidedText( + label, QtCore.Qt.ElideRight, label_rect.width() + ) + + # Maintain reference to state, so we can restore it once we're done + painter.save() + + painter.setFont(fonts["awesome6"]) + painter.setPen(QtGui.QPen(colors["idle"])) + painter.drawText(expander_rect, QtCore.Qt.AlignCenter, expander_icon) + + # Draw label + painter.setFont(fonts["h5"]) + painter.drawText(label_rect, label) + + # Ok, we're done, tidy up. + painter.restore() + + def sizeHint(self, option, index): + return QtCore.QSize(option.rect.width(), 20) + + class PluginDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for plugin header""" @@ -362,11 +495,6 @@ class PluginDelegate(QtWidgets.QStyledItemDelegate): return QtCore.QSize(option.rect.width(), 20) -class InstanceDelegate(OverviewGroupSection): - """Generic delegate for model items in proxy tree view""" - item_class = InstanceItemDelegate - - class ArtistDelegate(QtWidgets.QStyledItemDelegate): """Delegate used on Artist page""" From d8b5c9ab10834bfc87d605ed7510c27836d9a1b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:54:17 +0200 Subject: [PATCH 28/69] expander width is defined in constants --- pype/tools/pyblish_pype/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/pyblish_pype/constants.py b/pype/tools/pyblish_pype/constants.py index 5395d1fd0a..03536fb829 100644 --- a/pype/tools/pyblish_pype/constants.py +++ b/pype/tools/pyblish_pype/constants.py @@ -1,5 +1,7 @@ from Qt import QtCore +EXPANDER_WIDTH = 20 + def flags(*args, **kwargs): type_name = kwargs.pop("type_name", "Flags") From 977245f07d3197461aa2e61a2584e62d84aee2c9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:55:32 +0200 Subject: [PATCH 29/69] Created custom plugin view --- pype/tools/pyblish_pype/view.py | 36 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 450f56421c..0d129c6dff 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -84,8 +84,6 @@ class OverviewView(QtWidgets.QTreeView): self.setRootIsDecorated(False) self.setIndentation(0) - self.clicked.connect(self.item_expand) - def event(self, event): if not event.type() == QtCore.QEvent.KeyPress: return super(OverviewView, self).event(event) @@ -113,6 +111,24 @@ class OverviewView(QtWidgets.QTreeView): def focusOutEvent(self, event): self.selectionModel().clear() + def mouseReleaseEvent(self, event): + if event.button() in (QtCore.Qt.LeftButton, QtCore.Qt.RightButton): + # Deselect all group labels + indexes = self.selectionModel().selectedIndexes() + for index in indexes: + if index.data(Roles.TypeRole) == model.GroupType: + self.selectionModel().select( + index, QtCore.QItemSelectionModel.Deselect + ) + + return super(OverviewView, self).mouseReleaseEvent(event) + + +class PluginView(OverviewView): + def __init__(self, *args, **kwargs): + super(PluginView, self).__init__(*args, **kwargs) + self.clicked.connect(self.item_expand) + def item_expand(self, index): if index.data(Roles.TypeRole) == model.GroupType: if self.isExpanded(index): @@ -125,23 +141,19 @@ class OverviewView(QtWidgets.QTreeView): indexes = self.selectionModel().selectedIndexes() if len(indexes) == 1: index = indexes[0] - # If instance or Plugin - if index.data(Roles.TypeRole) in ( - model.InstanceType, model.PluginType + pos_index = self.indexAt(event.pos()) + # If instance or Plugin and is selected + if ( + index == pos_index + and index.data(Roles.TypeRole) == model.PluginType ): if event.pos().x() < 20: self.toggled.emit(index, None) elif event.pos().x() > self.width() - 20: self.show_perspective.emit(index) - # Deselect all group labels - for index in indexes: - if index.data(Roles.TypeRole) == model.GroupType: - self.selectionModel().select( - index, QtCore.QItemSelectionModel.Deselect - ) + return super(PluginView, self).mouseReleaseEvent(event) - return super(OverviewView, self).mouseReleaseEvent(event) class TerminalView(QtWidgets.QTreeView): From 49fe164c12ae52c11815d4a06d6fad9ed2b7e841 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:55:46 +0200 Subject: [PATCH 30/69] implemented view for instances --- pype/tools/pyblish_pype/view.py | 68 ++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 0d129c6dff..e4bc3a483e 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -1,6 +1,6 @@ from Qt import QtCore, QtWidgets from . import model -from .constants import Roles +from .constants import Roles, EXPANDER_WIDTH # Imported when used widgets = None @@ -155,6 +155,72 @@ class PluginView(OverviewView): return super(PluginView, self).mouseReleaseEvent(event) +class InstanceView(OverviewView): + def __init__(self, parent=None): + super(InstanceView, self).__init__(parent) + self.viewport().setMouseTracking(True) + + def mouseMoveEvent(self, event): + index = self.indexAt(event.pos()) + if index.data(Roles.TypeRole) == model.GroupType: + self.update(index) + + def item_expand(self, index, expand=None): + if expand is None: + expand = not self.isExpanded(index) + + if expand: + self.expand(index) + else: + self.collapse(index) + + def group_toggle(self, index): + model = index.model() + + chilren_indexes_checked = [] + chilren_indexes_unchecked = [] + for idx in range(model.rowCount(index)): + child_index = model.index(idx, 0, index) + if not child_index.data(Roles.IsEnabledRole): + continue + + if child_index.data(QtCore.Qt.CheckStateRole): + chilren_indexes_checked.append(child_index) + else: + chilren_indexes_unchecked.append(child_index) + + if chilren_indexes_checked: + to_change_indexes = chilren_indexes_checked + new_state = False + else: + to_change_indexes = chilren_indexes_unchecked + new_state = True + + for index in to_change_indexes: + model.setData(index, new_state, QtCore.Qt.CheckStateRole) + self.toggled.emit(index, new_state) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + indexes = self.selectionModel().selectedIndexes() + if len(indexes) == 1: + index = indexes[0] + pos_index = self.indexAt(event.pos()) + if index == pos_index: + # If instance or Plugin + if index.data(Roles.TypeRole) == model.InstanceType: + if event.pos().x() < 20: + self.toggled.emit(index, None) + elif event.pos().x() > self.width() - 20: + self.show_perspective.emit(index) + else: + if event.pos().x() < EXPANDER_WIDTH: + self.item_expand(index) + else: + self.group_toggle(index) + self.item_expand(index, True) + return super(InstanceView, self).mouseReleaseEvent(event) + class TerminalView(QtWidgets.QTreeView): # An item is requesting to be toggled, with optional forced-state From faba238fb06972d22154a9ade3c6f88898033adb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:56:09 +0200 Subject: [PATCH 31/69] added colors for instance delegate --- pype/tools/pyblish_pype/delegate.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pype/tools/pyblish_pype/delegate.py b/pype/tools/pyblish_pype/delegate.py index 2f1e9266ea..641158295b 100644 --- a/pype/tools/pyblish_pype/delegate.py +++ b/pype/tools/pyblish_pype/delegate.py @@ -5,7 +5,7 @@ from Qt import QtWidgets, QtGui, QtCore from . import model from .awesome import tags as awesome from .constants import ( - PluginStates, InstanceStates, PluginActionStates, Roles + PluginStates, InstanceStates, PluginActionStates, Roles, EXPANDER_WIDTH ) colors = { @@ -19,7 +19,12 @@ colors = { "hover": QtGui.QColor(255, 255, 255, 10), "selected": QtGui.QColor(255, 255, 255, 20), "outline": QtGui.QColor("#333"), - "group": QtGui.QColor("#333") + "group": QtGui.QColor("#333"), + "group-hover": QtGui.QColor("#3c3c3c"), + "group-selected": QtGui.QColor("#555555"), + "expander-bg": QtGui.QColor("#222"), + "expander-hover": QtGui.QColor("#2d6c9f"), + "expander-selected": QtGui.QColor("#3784c5"), } scale_factors = {"darwin": 1.5} From ec6f10c4aadd2bc61326020b52de856ee058ece9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 26 Jun 2020 14:56:23 +0200 Subject: [PATCH 32/69] there are used instance and plugin views in main app --- pype/tools/pyblish_pype/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 3c7808496c..7d79e0e26c 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -160,14 +160,14 @@ class Window(QtWidgets.QDialog): # TODO add parent overview_page = QtWidgets.QWidget() - overview_instance_view = view.OverviewView(parent=overview_page) + overview_instance_view = view.InstanceView(parent=overview_page) overview_instance_delegate = delegate.InstanceDelegate( parent=overview_instance_view ) overview_instance_view.setItemDelegate(overview_instance_delegate) overview_instance_view.setModel(instance_model) - overview_plugin_view = view.OverviewView(parent=overview_page) + overview_plugin_view = view.PluginView(parent=overview_page) overview_plugin_delegate = delegate.PluginDelegate( parent=overview_plugin_view ) From fb5abe71b6e0879768160f5e0ea43ff8b070ff1e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 27 Jun 2020 08:42:05 +0200 Subject: [PATCH 33/69] fixed multiple selection in instance view --- pype/tools/pyblish_pype/view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index e4bc3a483e..477303eae8 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -164,6 +164,7 @@ class InstanceView(OverviewView): index = self.indexAt(event.pos()) if index.data(Roles.TypeRole) == model.GroupType: self.update(index) + super(InstanceView, self).mouseMoveEvent(event) def item_expand(self, index, expand=None): if expand is None: From 97f30b6e691869d99d3b5f9611b85ad8642435e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 27 Jun 2020 08:42:58 +0200 Subject: [PATCH 34/69] instance group has right radius --- pype/tools/pyblish_pype/delegate.py | 85 ++++++++++++++++++----------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/pype/tools/pyblish_pype/delegate.py b/pype/tools/pyblish_pype/delegate.py index 641158295b..8fce63ee28 100644 --- a/pype/tools/pyblish_pype/delegate.py +++ b/pype/tools/pyblish_pype/delegate.py @@ -14,17 +14,16 @@ colors = { "ok": QtGui.QColor("#77AE24"), "active": QtGui.QColor("#99CEEE"), "idle": QtCore.Qt.white, - "font": QtGui.QColor("#DDD"), "inactive": QtGui.QColor("#888"), "hover": QtGui.QColor(255, 255, 255, 10), "selected": QtGui.QColor(255, 255, 255, 20), "outline": QtGui.QColor("#333"), "group": QtGui.QColor("#333"), "group-hover": QtGui.QColor("#3c3c3c"), - "group-selected": QtGui.QColor("#555555"), + "group-selected-hover": QtGui.QColor("#555555"), "expander-bg": QtGui.QColor("#222"), "expander-hover": QtGui.QColor("#2d6c9f"), - "expander-selected": QtGui.QColor("#3784c5"), + "expander-selected-hover": QtGui.QColor("#3784c5") } scale_factors = {"darwin": 1.5} @@ -287,6 +286,8 @@ class InstanceItemDelegate(QtWidgets.QStyledItemDelegate): class InstanceDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for instance header""" + radius = 8.0 + def __init__(self, parent): super(InstanceDelegate, self).__init__(parent) self.item_delegate = InstanceItemDelegate(parent) @@ -321,18 +322,32 @@ class InstanceDelegate(QtWidgets.QStyledItemDelegate): expander_rect.height() ) - radius = 8.0 width = float(expander_rect.width()) height = float(expander_rect.height()) + x_pos = expander_rect.x() y_pos = expander_rect.y() + x_radius = min(self.radius, width / 2) + y_radius = min(self.radius, height / 2) + x_radius2 = x_radius * 2 + y_radius2 = y_radius * 2 + expander_path = QtGui.QPainterPath() - expander_path.moveTo(x_pos + width - radius, y_pos) + expander_path.moveTo(x_pos, y_pos + y_radius) + expander_path.arcTo( + x_pos, y_pos, + x_radius2, y_radius2, + 180.0, -90.0 + ) expander_path.lineTo(x_pos + width, y_pos) expander_path.lineTo(x_pos + width, y_pos + height) - expander_path.lineTo(x_pos + width - radius, y_pos + height) - expander_path.arcTo(x_pos, y_pos, radius, height, 270.0, -180.0) + expander_path.lineTo(x_pos + x_radius, y_pos + height) + expander_path.arcTo( + x_pos, y_pos + height - y_radius2, + x_radius2, y_radius2, + 270.0, -90.0 + ) expander_path.closeSubpath() width = float(remainder_rect.width()) @@ -340,42 +355,50 @@ class InstanceDelegate(QtWidgets.QStyledItemDelegate): x_pos = remainder_rect.x() y_pos = remainder_rect.y() + x_radius = min(self.radius, width / 2) + y_radius = min(self.radius, height / 2) + x_radius2 = x_radius * 2 + y_radius2 = y_radius * 2 + remainder_path = QtGui.QPainterPath() - remainder_path.moveTo(x_pos, y_pos) - remainder_path.lineTo(x_pos + width - radius, y_pos) + remainder_path.moveTo(x_pos + width, y_pos + height - y_radius) remainder_path.arcTo( - x_pos + width - radius, y_pos, - radius, height, - 90.0, -180.0 + x_pos + width - x_radius2, y_pos + height - y_radius2, + x_radius2, y_radius2, + 0.0, -90.0 ) remainder_path.lineTo(x_pos, y_pos + height) remainder_path.lineTo(x_pos, y_pos) + remainder_path.lineTo(x_pos + width - x_radius, y_pos) + remainder_path.arcTo( + x_pos + width - x_radius2, y_pos, + x_radius2, y_radius2, + 90.0, -90.0 + ) remainder_path.closeSubpath() - mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) - painted_expander = False - painted_remainder = False - if option.state & QtWidgets.QStyle.State_Selected: - if expander_rect.contains(mouse_pos): - painter.fillPath(expander_path, colors["expander-selected"]) - painted_expander = True - else: - painter.fillPath(remainder_path, colors["group-selected"]) - painted_remainder = True + painter.fillPath(expander_path, colors["expander-bg"]) + painter.fillPath(remainder_path, colors["group"]) - elif option.state & QtWidgets.QStyle.State_MouseOver: + mouse_pos = option.widget.mapFromGlobal(QtGui.QCursor.pos()) + selected = option.state & QtWidgets.QStyle.State_Selected + hovered = option.state & QtWidgets.QStyle.State_MouseOver + + if selected and hovered: + if expander_rect.contains(mouse_pos): + painter.fillPath( + expander_path, colors["expander-selected-hover"] + ) + else: + painter.fillPath( + remainder_path, colors["group-selected-hover"] + ) + + elif hovered: if expander_rect.contains(mouse_pos): painter.fillPath(expander_path, colors["expander-hover"]) - painted_expander = True else: painter.fillPath(remainder_path, colors["group-hover"]) - painted_remainder = True - - if not painted_expander: - painter.fillPath(expander_path, colors["expander-bg"]) - - if not painted_remainder: - painter.fillPath(remainder_path, colors["group"]) text_height = font_metrics["awesome6"].height() adjust_value = (expander_rect.height() - text_height) / 2 From 91d3d697e69edd593357ba98d980bf7885960d38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 27 Jun 2020 08:43:17 +0200 Subject: [PATCH 35/69] plugin group has same hover and selection colors --- pype/tools/pyblish_pype/delegate.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pype/tools/pyblish_pype/delegate.py b/pype/tools/pyblish_pype/delegate.py index 8fce63ee28..cb9123bf3a 100644 --- a/pype/tools/pyblish_pype/delegate.py +++ b/pype/tools/pyblish_pype/delegate.py @@ -443,8 +443,6 @@ class InstanceDelegate(QtWidgets.QStyledItemDelegate): class PluginDelegate(QtWidgets.QStyledItemDelegate): """Generic delegate for plugin header""" - item_class = None - def __init__(self, parent): super(PluginDelegate, self).__init__(parent) self.item_delegate = PluginItemDelegate(parent) @@ -471,7 +469,14 @@ class PluginDelegate(QtWidgets.QStyledItemDelegate): radius = 8.0 bg_path = QtGui.QPainterPath() bg_path.addRoundedRect(bg_rect, radius, radius) - painter.fillPath(bg_path, colors["group"]) + hovered = option.state & QtWidgets.QStyle.State_MouseOver + selected = option.state & QtWidgets.QStyle.State_Selected + if hovered and selected: + painter.fillPath(bg_path, colors["group-selected-hover"]) + elif hovered: + painter.fillPath(bg_path, colors["group-hover"]) + else: + painter.fillPath(bg_path, colors["group"]) expander_rect = QtCore.QRectF(bg_rect) expander_rect.setWidth(expander_rect.height()) @@ -510,12 +515,6 @@ class PluginDelegate(QtWidgets.QStyledItemDelegate): painter.setFont(fonts["h5"]) painter.drawText(label_rect, label) - if option.state & QtWidgets.QStyle.State_MouseOver: - painter.fillPath(bg_path, colors["hover"]) - - if option.state & QtWidgets.QStyle.State_Selected: - painter.fillPath(bg_path, colors["selected"]) - # Ok, we're done, tidy up. painter.restore() From 279b221e33d8a1cca99736326570c978fb9b3f39 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 28 Jun 2020 15:40:00 +0100 Subject: [PATCH 36/69] Initial working version for palettes. --- pype/hosts/harmony/__init__.py | 5 +- pype/plugins/global/publish/integrate_new.py | 3 +- .../harmony/load/load_imagesequence.py | 22 +++++ pype/plugins/harmony/load/load_palette.py | 93 +++++++++++++++++++ .../harmony/publish/collect_palettes.py | 45 +++++++++ .../harmony/publish/extract_palette.py | 34 +++++++ 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 pype/plugins/harmony/load/load_palette.py create mode 100644 pype/plugins/harmony/publish/collect_palettes.py create mode 100644 pype/plugins/harmony/publish/extract_palette.py diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 628397e777..4dc06cdf84 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -121,7 +121,10 @@ def check_inventory(): } func """ - outdated_nodes = [x["node"] for x in outdated_containers] + outdated_nodes = [] + for container in outdated_containers: + if container["loader"] == "ImageSequenceLoader": + outdated_nodes.append(container["name"]) harmony.send({"function": func, "args": [outdated_nodes]}) # Warn about outdated containers. diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 040ed9cd67..a33db2bcec 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -83,6 +83,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "textures", "action", "harmony.template", + "harmony.palette", "editorial" ] exclude_families = ["clip"] @@ -605,7 +606,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "type": "subset", "name": subset_name, "data": { - "families": instance.data.get('families') + "families": instance.data.get("families", []) }, "parent": asset["_id"] }).inserted_id diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index b56dba03d4..615188572e 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -3,6 +3,7 @@ import os import clique from avalon import api, harmony +import pype.lib copy_files = """function copyFile(srcFilename, dstFilename) { @@ -297,6 +298,27 @@ class ImageSequenceLoader(api.Loader): } ) + # Colour node. + func = """function func(args){ + for( var i =0; i <= args[0].length - 1; ++i) + { + var red_color = new ColorRGBA(255, 0, 0, 255); + var green_color = new ColorRGBA(0, 255, 0, 255); + if (args[1] == "red"){ + node.setColor(args[0], red_color); + } + if (args[1] == "green"){ + node.setColor(args[0], green_color); + } + } + } + func + """ + if pype.lib.is_latest(representation): + harmony.send({"function": func, "args": [node, "green"]}) + else: + harmony.send({"function": func, "args": [node, "red"]}) + harmony.imprint( node, {"representation": str(representation["_id"])} ) diff --git a/pype/plugins/harmony/load/load_palette.py b/pype/plugins/harmony/load/load_palette.py new file mode 100644 index 0000000000..cfb88ac841 --- /dev/null +++ b/pype/plugins/harmony/load/load_palette.py @@ -0,0 +1,93 @@ +import os +import shutil +import uuid + +from bson.objectid import ObjectId + +from avalon import api, harmony + + +class ImportPaletteLoader(api.Loader): + """Import palettes.""" + + families = ["harmony.palette"] + representations = ["plt"] + label = "Import Palette" + + def load(self, context, name=None, namespace=None, data=None): + name = self.load_palette(context["representation"]) + + return harmony.containerise( + name, + namespace, + name, + context, + self.__class__.__name__ + ) + + def load_palette(self, representation): + subset_name = representation["context"]["subset"] + name = subset_name.replace("palette", "") + name += "_{}".format(uuid.uuid4()) + + # Import new palette. + scene_path = harmony.send( + {"function": "scene.currentProjectPath"} + )["result"] + src = api.get_representation_path(representation) + dst = os.path.join( + scene_path, + "palette-library", + "{}.plt".format(name) + ) + shutil.copy(src, dst) + + func = """function func(args) + { + var palette_list = PaletteObjectManager.getScenePaletteList(); + var palette = palette_list.addPaletteAtLocation( + PaletteObjectManager.Constants.Location.SCENE, 0, args[0] + ); + for(var i=0; i < palette_list.numPalettes; ++i) + { + palette_list.movePaletteUp(palette.id); + } + return palette.id; + } + func + """ + harmony.send({"function": func, "args": [name]}) + + return name + + def remove(self, container): + # Replace any palettes with same name. + func = """function func(args) + { + var pom = PaletteObjectManager; + var palette_list = pom.getScenePaletteList(); + for(var i=0; i < palette_list.numPalettes; ++i) + { + var palette = palette_list.getPaletteByIndex(i); + if(palette.getName() == args[0]) + pom.removePaletteReferencesAndDeleteOnDisk(palette.id); + } + } + func + """ + harmony.send({"function": func, "args": [container["name"]]}) + + harmony.remove(container["name"]) + + harmony.save_scene() + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + self.remove(container) + name = self.load_palette(representation) + + container["representation"] = str(representation["_id"]) + container["name"] = name + harmony.imprint(name, container) diff --git a/pype/plugins/harmony/publish/collect_palettes.py b/pype/plugins/harmony/publish/collect_palettes.py new file mode 100644 index 0000000000..2a2c1066c0 --- /dev/null +++ b/pype/plugins/harmony/publish/collect_palettes.py @@ -0,0 +1,45 @@ +import os +import json + +import pyblish.api +from avalon import harmony + + +class CollectPalettes(pyblish.api.ContextPlugin): + """Gather palettes from scene when publishing templates.""" + + label = "Palettes" + order = pyblish.api.CollectorOrder + hosts = ["harmony"] + + def process(self, context): + func = """function func() + { + var palette_list = PaletteObjectManager.getScenePaletteList(); + + var palettes = {}; + for(var i=0; i < palette_list.numPalettes; ++i) + { + var palette = palette_list.getPaletteByIndex(i); + palettes[palette.getName()] = palette.id; + } + + return palettes; + } + func + """ + palettes = harmony.send({"function": func})["result"] + + for name, id in palettes.items(): + instance = context.create_instance(name) + instance.data.update({ + "id": id, + "family": "harmony.palette", + "asset": os.environ["AVALON_ASSET"], + "subset": "palette" + name + }) + self.log.info( + "Created instance:\n" + json.dumps( + instance.data, sort_keys=True, indent=4 + ) + ) diff --git a/pype/plugins/harmony/publish/extract_palette.py b/pype/plugins/harmony/publish/extract_palette.py new file mode 100644 index 0000000000..9bca005278 --- /dev/null +++ b/pype/plugins/harmony/publish/extract_palette.py @@ -0,0 +1,34 @@ +import os + +from avalon import harmony +import pype.api +import pype.hosts.harmony + + +class ExtractPalette(pype.api.Extractor): + """Extract palette.""" + + label = "Extract Palette" + hosts = ["harmony"] + families = ["harmony.palette"] + + def process(self, instance): + func = """function func(args) + { + var palette_list = PaletteObjectManager.getScenePaletteList(); + var palette = palette_list.getPaletteById(args[0]); + return (palette.getPath() + "/" + palette.getName() + ".plt"); + } + func + """ + palette_file = harmony.send( + {"function": func, "args": [instance.data["id"]]} + )["result"] + + representation = { + "name": "plt", + "ext": "plt", + "files": os.path.basename(palette_file), + "stagingDir": os.path.dirname(palette_file) + } + instance.data["representations"] = [representation] From 1a15dc78ce8dc41a8bd886e03bab29b91b73cbfe Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 28 Jun 2020 16:23:05 +0100 Subject: [PATCH 37/69] Convert imageseuqnece loader --- pype/hosts/harmony/__init__.py | 4 +++- pype/plugins/harmony/load/load_imagesequence.py | 12 +++++++----- pype/plugins/harmony/load/load_palette.py | 2 -- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 4dc06cdf84..3345c3134a 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -124,7 +124,9 @@ def check_inventory(): outdated_nodes = [] for container in outdated_containers: if container["loader"] == "ImageSequenceLoader": - outdated_nodes.append(container["name"]) + outdated_nodes.append( + harmony.find_node_by_name(container["name"]), "READ" + ) harmony.send({"function": func, "args": [outdated_nodes]}) # Warn about outdated containers. diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index 615188572e..f81018d0fb 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -1,4 +1,5 @@ import os +import uuid import clique @@ -252,15 +253,15 @@ class ImageSequenceLoader(api.Loader): ).replace("\\", "/") ) + name = context["subset"]["name"] + name += "_{}".format(uuid.uuid4()) read_node = harmony.send( { "function": copy_files + import_files, - "args": ["Top", files, context["subset"]["name"], 1] + "args": ["Top", files, name, 1] } )["result"] - self[:] = [read_node] - return harmony.containerise( name, namespace, @@ -270,7 +271,7 @@ class ImageSequenceLoader(api.Loader): ) def update(self, container, representation): - node = container.pop("node") + node = harmony.find_node_by_name(container["name"], "READ") path = api.get_representation_path(representation) collections, remainder = clique.assemble( @@ -324,7 +325,8 @@ class ImageSequenceLoader(api.Loader): ) def remove(self, container): - node = container.pop("node") + node = harmony.find_node_by_name(container["name"], "READ") + func = """function deleteNode(_node) { node.deleteNode(_node, true, true); diff --git a/pype/plugins/harmony/load/load_palette.py b/pype/plugins/harmony/load/load_palette.py index cfb88ac841..44aaf76aa5 100644 --- a/pype/plugins/harmony/load/load_palette.py +++ b/pype/plugins/harmony/load/load_palette.py @@ -2,8 +2,6 @@ import os import shutil import uuid -from bson.objectid import ObjectId - from avalon import api, harmony From 4ccc052f83dae3b346a7e0a0c8ccca05c4d9a9bd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 28 Jun 2020 17:16:17 +0100 Subject: [PATCH 38/69] Fix check inventory --- pype/hosts/harmony/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 3345c3134a..3cae695852 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -125,7 +125,7 @@ def check_inventory(): for container in outdated_containers: if container["loader"] == "ImageSequenceLoader": outdated_nodes.append( - harmony.find_node_by_name(container["name"]), "READ" + harmony.find_node_by_name(container["name"], "READ") ) harmony.send({"function": func, "args": [outdated_nodes]}) From 17546da1a2ecc86fc228b17e34b7d898299d6d0a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 28 Jun 2020 17:34:05 +0100 Subject: [PATCH 39/69] Ftrack timeout needs to look at AVALON_TIMEOUT --- pype/modules/ftrack/lib/custom_db_connector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/custom_db_connector.py b/pype/modules/ftrack/lib/custom_db_connector.py index a734b3f80a..f8a188466c 100644 --- a/pype/modules/ftrack/lib/custom_db_connector.py +++ b/pype/modules/ftrack/lib/custom_db_connector.py @@ -9,6 +9,7 @@ import time import logging import functools import atexit +import os # Third-party dependencies import pymongo @@ -62,7 +63,7 @@ def check_active_table(func): class DbConnector: log = logging.getLogger(__name__) - timeout = 1000 + timeout = int(os.environ["AVALON_TIMEOUT"]) def __init__( self, uri, port=None, database_name=None, table_name=None From 1fffb6fcf48e6b3b43f9522b4402bf4516392816 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jun 2020 09:36:12 +0100 Subject: [PATCH 40/69] 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 41/69] 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}") From 8de72755c478b71f14f25529c1b52d4324d1d464 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 29 Jun 2020 17:21:54 +0100 Subject: [PATCH 42/69] Layout support for namespaces --- pype/hosts/blender/plugin.py | 30 ++++++- pype/plugins/blender/load/load_layout.py | 105 ++++++++++++----------- pype/plugins/blender/load/load_model.py | 51 +++++++---- pype/plugins/blender/load/load_rig.py | 66 ++++++++------ 4 files changed, 156 insertions(+), 96 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index 5a2596c9d8..eff41956e8 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -24,13 +24,17 @@ def asset_namespace( asset: str, subset: str ) -> str: """Return a unique namespace based on the asset name.""" - avalon_containers = bpy.data.collections.get( - blender.pipeline.AVALON_CONTAINERS - ) + avalon_containers = [ + c for c in bpy.data.collections + if c.name == 'AVALON_CONTAINERS' + ] + loaded_assets = [] + for c in avalon_containers: + loaded_assets.extend(c.children) if avalon_containers is None: return "1" collections_names = [ - c.name for c in avalon_containers.children + c.name for c in loaded_assets ] count = 1 name = f"{asset_name(asset, subset, str(count))}_CON" @@ -40,6 +44,12 @@ def asset_namespace( return str(count) +def prepare_data(data, container_name): + name = data.name + data = data.make_local() + data.name = f"{name}:{container_name}" + + def create_blender_context(active: Optional[bpy.types.Object] = None, selected: Optional[bpy.types.Object] = None,): """Create a new Blender context. If an object is passed as @@ -67,6 +77,18 @@ def create_blender_context(active: Optional[bpy.types.Object] = None, raise Exception("Could not create a custom Blender context.") +def get_parent_collection(collection): + """Get the parent of the input collection""" + check_list = [bpy.context.scene.collection] + + for c in check_list: + if collection.name in c.children.keys(): + return c + check_list.extend(c.children) + + return None + + class AssetLoader(api.Loader): """A basic AssetLoader for Blender diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index 0c1032c4fb..5d3f7d92cf 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -7,20 +7,14 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.plugin - +import pype.hosts.blender.plugin as plugin logger = logging.getLogger("pype").getChild( "blender").getChild("load_layout") -class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): - """Load animations from a .blend file. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. - """ +class BlendLayoutLoader(plugin.AssetLoader): + """Load layout from a .blend file.""" families = ["layout"] representations = ["blend"] @@ -29,24 +23,25 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - + def _remove(self, objects, obj_container): for obj in objects: - if obj.type == 'ARMATURE': bpy.data.armatures.remove(obj.data) elif obj.type == 'MESH': bpy.data.meshes.remove(obj.data) + elif obj.type == 'CAMERA': + bpy.data.cameras.remove(obj.data) + elif obj.type == 'CURVE': + bpy.data.curves.remove(obj.data) - for element_container in bpy.data.collections[lib_container].children: + for element_container in obj_container.children: for child in element_container.children: bpy.data.collections.remove(child) bpy.data.collections.remove(element_container) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + bpy.data.collections.remove(obj_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 @@ -58,26 +53,38 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) layout_container = scene.collection.children[lib_container].make_local() + layout_container.name = container_name - meshes = [] + objects_local_types = ['MESH', 'CAMERA', 'CURVE'] + + objects = [] armatures = [] - objects_list = [] + containers = list(layout_container.children) - for element_container in layout_container.children: - element_container.make_local() - meshes.extend([obj for obj in element_container.objects if obj.type == 'MESH']) - armatures.extend([obj for obj in element_container.objects if obj.type == 'ARMATURE']) - for child in element_container.children: - child.make_local() - meshes.extend(child.objects) + for container in layout_container.children: + if container.name == blender.pipeline.AVALON_CONTAINERS: + containers.remove(container) + + for container in containers: + container.make_local() + objects.extend([ + obj for obj in container.objects + if obj.type in objects_local_types + ]) + armatures.extend([ + obj for obj in container.objects + if obj.type == 'ARMATURE' + ]) + containers.extend(list(container.children)) # Link meshes first, then armatures. # The armature is unparented for all the non-local meshes, # when it is made local. - for obj in meshes + armatures: - obj = obj.make_local() - obj.data.make_local() + for obj in objects + armatures: + obj.make_local() + if obj.data: + obj.data.make_local() if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -85,18 +92,16 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): avalon_info = obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) - action = actions.get( obj.name, None ) + action = actions.get(obj.name, None) if obj.type == 'ARMATURE' and action is not None: obj.animation_data.action = action - objects_list.append(obj) - layout_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') - return objects_list + return layout_container def process_asset( self, context: dict, name: str, namespace: Optional[str] = None, @@ -113,13 +118,17 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): 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( + lib_container = plugin.asset_name( + asset, subset + ) + namespace = namespace or plugin.asset_namespace( + asset, subset + ) + container_name = plugin.asset_name( asset, subset, namespace ) container = bpy.data.collections.new(lib_container) - container.name = container_name blender.pipeline.containerise_existing( container, name, @@ -134,11 +143,13 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process( + obj_container = self._process( libpath, lib_container, container_name, {}) + container_metadata["obj_container"] = obj_container + # Save the list of objects in the metadata container - container_metadata["objects"] = objects_list + container_metadata["objects"] = obj_container.all_objects nodes = list(container.objects) nodes.append(container) @@ -157,7 +168,6 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): Warning: No nested collections are supported at the moment! """ - collection = bpy.data.collections.get( container["objectName"] ) @@ -189,8 +199,11 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - collection_libpath = collection_metadata["libpath"] + objects = collection_metadata["objects"] + lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] + normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) ) @@ -206,24 +219,20 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): logger.info("Library already loaded, not updating...") return - objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] - actions = {} for obj in objects: - if obj.type == 'ARMATURE': - actions[obj.name] = obj.animation_data.action - self._remove(objects, lib_container) + self._remove(objects, obj_container) - objects_list = self._process( + obj_container = 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["obj_container"] = obj_container + collection_metadata["objects"] = obj_container.all_objects collection_metadata["libpath"] = str(libpath) collection_metadata["representation"] = str(representation["_id"]) @@ -255,9 +264,9 @@ class BlendLayoutLoader(pype.hosts.blender.plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) objects = collection_metadata["objects"] - lib_container = collection_metadata["lib_container"] + obj_container = collection_metadata["obj_container"] - self._remove(objects, lib_container) + self._remove(objects, obj_container) bpy.data.collections.remove(collection) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 6f34b40e70..7b2ade4570 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -34,31 +34,32 @@ class BlendModelLoader(plugin.AssetLoader): bpy.data.collections.remove(container) - def prepare_data(self, data, container_name): - name = data.name - data = data.make_local() - data.name = f"{name}:{container_name}" - - def _process(self, libpath, lib_container, container_name): + def _process( + self, libpath, lib_container, container_name, + parent_collection + ): 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 + parent = parent_collection - scene.collection.children.link(bpy.data.collections[lib_container]) + if parent is None: + parent = bpy.context.scene.collection - model_container = scene.collection.children[lib_container].make_local() + parent.children.link(bpy.data.collections[lib_container]) + + model_container = parent.children[lib_container].make_local() model_container.name = container_name for obj in model_container.objects: - self.prepare_data(obj, container_name) - self.prepare_data(obj.data, container_name) + plugin.prepare_data(obj, container_name) + plugin.prepare_data(obj.data, container_name) for material_slot in obj.material_slots: - self.prepare_data(material_slot.material, container_name) + plugin.prepare_data(material_slot.material, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -114,7 +115,7 @@ class BlendModelLoader(plugin.AssetLoader): container_metadata["lib_container"] = lib_container obj_container = self._process( - libpath, lib_container, container_name) + libpath, lib_container, container_name, None) container_metadata["obj_container"] = obj_container @@ -169,9 +170,16 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) collection_libpath = collection_metadata["libpath"] - objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects + + container_name = obj_container.name normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) @@ -188,10 +196,12 @@ class BlendModelLoader(plugin.AssetLoader): logger.info("Library already loaded, not updating...") return + parent = plugin.get_parent_collection(obj_container) + self._remove(objects, obj_container) obj_container = self._process( - str(libpath), lib_container, collection.name) + str(libpath), lib_container, container_name, parent) # Save the list of objects in the metadata container collection_metadata["obj_container"] = obj_container @@ -223,8 +233,13 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - objects = collection_metadata["objects"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects self._remove(objects, obj_container) diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 41343e9c3a..7ac8a72993 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -26,54 +26,54 @@ class BlendRigLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" - def _remove(self, objects, lib_container): - + def _remove(self, objects, obj_container): for obj in objects: if obj.type == 'ARMATURE': bpy.data.armatures.remove(obj.data) elif obj.type == 'MESH': bpy.data.meshes.remove(obj.data) - for child in bpy.data.collections[lib_container].children: + for child in obj_container.children: bpy.data.collections.remove(child) - bpy.data.collections.remove(bpy.data.collections[lib_container]) + bpy.data.collections.remove(obj_container) - def prepare_data(self, data, container_name): - name = data.name - data = data.make_local() - data.name = f"{name}:{container_name}" - - def _process(self, libpath, lib_container, container_name, action): + def _process( + self, libpath, lib_container, container_name, + action, parent_collection + ): 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 + parent = parent_collection - scene.collection.children.link(bpy.data.collections[lib_container]) + if parent is None: + parent = bpy.context.scene.collection + + parent.children.link(bpy.data.collections[lib_container]) - rig_container = scene.collection.children[lib_container].make_local() + rig_container = parent.children[lib_container].make_local() rig_container.name = container_name meshes = [] armatures = [ - obj for obj in rig_container.objects if obj.type == 'ARMATURE'] - - objects_list = [] + obj for obj in rig_container.objects + if obj.type == 'ARMATURE' + ] for child in rig_container.children: - self.prepare_data(child, container_name) - meshes.extend( child.objects ) + plugin.prepare_data(child, container_name) + meshes.extend(child.objects) # Link meshes first, then armatures. # The armature is unparented for all the non-local meshes, # when it is made local. for obj in meshes + armatures: - self.prepare_data(obj, container_name) - self.prepare_data(obj.data, container_name) + plugin.prepare_data(obj, container_name) + plugin.prepare_data(obj.data, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): obj[blender.pipeline.AVALON_PROPERTY] = dict() @@ -131,7 +131,7 @@ class BlendRigLoader(plugin.AssetLoader): container_metadata["lib_container"] = lib_container obj_container = self._process( - libpath, lib_container, container_name, None) + libpath, lib_container, container_name, None, None) container_metadata["obj_container"] = obj_container @@ -186,9 +186,16 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) collection_libpath = collection_metadata["libpath"] - objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects + + container_name = obj_container.name normalized_collection_libpath = ( str(Path(bpy.path.abspath(collection_libpath)).resolve()) @@ -211,10 +218,12 @@ class BlendRigLoader(plugin.AssetLoader): action = armatures[0].animation_data.action + parent = plugin.get_parent_collection(obj_container) + self._remove(objects, obj_container) obj_container = self._process( - str(libpath), lib_container, collection.name, action) + str(libpath), lib_container, container_name, action, parent) # Save the list of objects in the metadata container collection_metadata["obj_container"] = obj_container @@ -249,8 +258,13 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - objects = collection_metadata["objects"] - obj_container = collection_metadata["obj_container"] + + obj_container = [ + c for c in bpy.data.collections + if (c.name == collection_metadata["obj_container"].name and + c.library is None) + ][0] + objects = obj_container.all_objects self._remove(objects, obj_container) From 52e2d785c10a0fbf2271b654db58ea22a39f15e5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 30 Jun 2020 11:35:24 +0100 Subject: [PATCH 43/69] Small improvements and Pep8 compliance --- pype/hosts/blender/plugin.py | 11 ++++++--- pype/plugins/blender/load/load_layout.py | 15 +++++------- pype/plugins/blender/load/load_model.py | 24 +++++++------------ pype/plugins/blender/load/load_rig.py | 30 ++++++++++-------------- 4 files changed, 35 insertions(+), 45 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index eff41956e8..b0420fddfc 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -31,10 +31,8 @@ def asset_namespace( loaded_assets = [] for c in avalon_containers: loaded_assets.extend(c.children) - if avalon_containers is None: - return "1" collections_names = [ - c.name for c in loaded_assets + c.name for c in loaded_assets ] count = 1 name = f"{asset_name(asset, subset, str(count))}_CON" @@ -89,6 +87,13 @@ def get_parent_collection(collection): return None +def get_local_collection_with_name(name): + for collection in bpy.data.collections: + if collection.name == name and collection.library is None: + return collection + return None + + class AssetLoader(api.Loader): """A basic AssetLoader for Blender diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index 5d3f7d92cf..e9e128bb7c 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -9,9 +9,6 @@ from avalon import api, blender import bpy import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild( - "blender").getChild("load_layout") - class BlendLayoutLoader(plugin.AssetLoader): """Load layout from a .blend file.""" @@ -65,15 +62,15 @@ class BlendLayoutLoader(plugin.AssetLoader): for container in layout_container.children: if container.name == blender.pipeline.AVALON_CONTAINERS: containers.remove(container) - + for container in containers: container.make_local() objects.extend([ - obj for obj in container.objects + obj for obj in container.objects if obj.type in objects_local_types ]) armatures.extend([ - obj for obj in container.objects + obj for obj in container.objects if obj.type == 'ARMATURE' ]) containers.extend(list(container.children)) @@ -175,7 +172,7 @@ class BlendLayoutLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -210,13 +207,13 @@ class BlendLayoutLoader(plugin.AssetLoader): normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( + self.log.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...") + self.log.info("Library already loaded, not updating...") return actions = {} diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 7b2ade4570..ad4b35eb03 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -9,8 +9,6 @@ from avalon import api, blender import bpy import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_model") - class BlendModelLoader(plugin.AssetLoader): """Load models from a .blend file. @@ -145,7 +143,7 @@ class BlendModelLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -172,11 +170,9 @@ class BlendModelLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects container_name = obj_container.name @@ -187,13 +183,13 @@ class BlendModelLoader(plugin.AssetLoader): normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( + self.log.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...") + self.log.info("Library already loaded, not updating...") return parent = plugin.get_parent_collection(obj_container) @@ -234,11 +230,9 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects self._remove(objects, obj_container) diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 7ac8a72993..860cae71ba 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -9,8 +9,6 @@ from avalon import api, blender import bpy import pype.hosts.blender.plugin as plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_rig") - class BlendRigLoader(plugin.AssetLoader): """Load rigs from a .blend file. @@ -39,7 +37,7 @@ class BlendRigLoader(plugin.AssetLoader): bpy.data.collections.remove(obj_container) def _process( - self, libpath, lib_container, container_name, + self, libpath, lib_container, container_name, action, parent_collection ): relative = bpy.context.preferences.filepaths.use_relative_paths @@ -52,7 +50,7 @@ class BlendRigLoader(plugin.AssetLoader): if parent is None: parent = bpy.context.scene.collection - + parent.children.link(bpy.data.collections[lib_container]) rig_container = parent.children[lib_container].make_local() @@ -60,7 +58,7 @@ class BlendRigLoader(plugin.AssetLoader): meshes = [] armatures = [ - obj for obj in rig_container.objects + obj for obj in rig_container.objects if obj.type == 'ARMATURE' ] @@ -161,7 +159,7 @@ class BlendRigLoader(plugin.AssetLoader): libpath = Path(api.get_representation_path(representation)) extension = libpath.suffix.lower() - logger.info( + self.log.info( "Container: %s\nRepresentation: %s", pformat(container, indent=2), pformat(representation, indent=2), @@ -188,11 +186,9 @@ class BlendRigLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects container_name = obj_container.name @@ -203,13 +199,13 @@ class BlendRigLoader(plugin.AssetLoader): normalized_libpath = ( str(Path(bpy.path.abspath(str(libpath))).resolve()) ) - logger.debug( + self.log.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...") + self.log.info("Library already loaded, not updating...") return # Get the armature of the rig @@ -259,11 +255,9 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = [ - c for c in bpy.data.collections - if (c.name == collection_metadata["obj_container"].name and - c.library is None) - ][0] + obj_container = get_local_collection_with_name( + collection_metadata["obj_container"].name + ) objects = obj_container.all_objects self._remove(objects, obj_container) From aa99cc14b8f341ed01c69a671d1d46051fd75d74 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 30 Jun 2020 11:42:27 +0100 Subject: [PATCH 44/69] Fix undefined function --- pype/plugins/blender/load/load_model.py | 4 ++-- pype/plugins/blender/load/load_rig.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index ad4b35eb03..0013ccb90a 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -170,7 +170,7 @@ class BlendModelLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects @@ -230,7 +230,7 @@ class BlendModelLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 860cae71ba..d9e4495090 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -186,7 +186,7 @@ class BlendRigLoader(plugin.AssetLoader): collection_libpath = collection_metadata["libpath"] lib_container = collection_metadata["lib_container"] - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects @@ -255,7 +255,7 @@ class BlendRigLoader(plugin.AssetLoader): collection_metadata = collection.get( blender.pipeline.AVALON_PROPERTY) - obj_container = get_local_collection_with_name( + obj_container = plugin.get_local_collection_with_name( collection_metadata["obj_container"].name ) objects = obj_container.all_objects From 1ebc6d351848f60895c8e1175851b099aa758e70 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 30 Jun 2020 16:34:20 +0100 Subject: [PATCH 45/69] Set start and end frames when opening file --- pype/hosts/blender/__init__.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pype/hosts/blender/__init__.py b/pype/hosts/blender/__init__.py index a6d3cd82ef..52cc53f05c 100644 --- a/pype/hosts/blender/__init__.py +++ b/pype/hosts/blender/__init__.py @@ -2,9 +2,11 @@ import os import sys import traceback -from avalon import api as avalon +from avalon import api as avalon, pipeline, blender from pyblish import api as pyblish +import bpy + from pype import PLUGINS_DIR PUBLISH_PATH = os.path.join(PLUGINS_DIR, "blender", "publish") @@ -25,6 +27,8 @@ def install(): avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH)) avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH)) + avalon.on("open", on_open) + def uninstall(): """Uninstall Blender configuration for Avalon.""" @@ -32,3 +36,17 @@ def uninstall(): pyblish.deregister_plugin_path(str(PUBLISH_PATH)) avalon.deregister_plugin_path(avalon.Loader, str(LOAD_PATH)) avalon.deregister_plugin_path(avalon.Creator, str(CREATE_PATH)) + + +def on_open(arg1, arg2): + + from avalon import io + + asset_name = io.Session["AVALON_ASSET"] + asset_doc = io.find_one({ + "type": "asset", + "name": asset_name + }) + + bpy.context.scene.frame_start = asset_doc["data"]["frameStart"] + bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"] From af8bcfafaadfa6139aec6a2b5f49284875714d13 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Jul 2020 10:33:03 +0100 Subject: [PATCH 46/69] Only overwrite the .plt file and warn users about visual changes. --- pype/plugins/harmony/load/load_palette.py | 47 ++++++----------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/pype/plugins/harmony/load/load_palette.py b/pype/plugins/harmony/load/load_palette.py index 44aaf76aa5..001758d5a8 100644 --- a/pype/plugins/harmony/load/load_palette.py +++ b/pype/plugins/harmony/load/load_palette.py @@ -1,8 +1,8 @@ import os import shutil -import uuid from avalon import api, harmony +from avalon.vendor import Qt class ImportPaletteLoader(api.Loader): @@ -26,9 +26,8 @@ class ImportPaletteLoader(api.Loader): def load_palette(self, representation): subset_name = representation["context"]["subset"] name = subset_name.replace("palette", "") - name += "_{}".format(uuid.uuid4()) - # Import new palette. + # Overwrite palette on disk. scene_path = harmony.send( {"function": "scene.currentProjectPath"} )["result"] @@ -40,45 +39,21 @@ class ImportPaletteLoader(api.Loader): ) shutil.copy(src, dst) - func = """function func(args) - { - var palette_list = PaletteObjectManager.getScenePaletteList(); - var palette = palette_list.addPaletteAtLocation( - PaletteObjectManager.Constants.Location.SCENE, 0, args[0] - ); - for(var i=0; i < palette_list.numPalettes; ++i) - { - palette_list.movePaletteUp(palette.id); - } - return palette.id; - } - func - """ - harmony.send({"function": func, "args": [name]}) + harmony.save_scene() + + # Dont allow instances with the same name. + message_box = Qt.QtWidgets.QMessageBox() + message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning) + msg = "Updated {}.".format(subset_name) + msg += " You need to reload the scene to see the changes." + message_box.setText(msg) + message_box.exec_() return name def remove(self, container): - # Replace any palettes with same name. - func = """function func(args) - { - var pom = PaletteObjectManager; - var palette_list = pom.getScenePaletteList(); - for(var i=0; i < palette_list.numPalettes; ++i) - { - var palette = palette_list.getPaletteByIndex(i); - if(palette.getName() == args[0]) - pom.removePaletteReferencesAndDeleteOnDisk(palette.id); - } - } - func - """ - harmony.send({"function": func, "args": [container["name"]]}) - harmony.remove(container["name"]) - harmony.save_scene() - def switch(self, container, representation): self.update(container, representation) From 138131d7e0e35699090ad613e110e4effe0c3d2f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 1 Jul 2020 11:09:34 +0100 Subject: [PATCH 47/69] Set start and end frames when opening Blender --- pype/hosts/blender/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pype/hosts/blender/__init__.py b/pype/hosts/blender/__init__.py index 52cc53f05c..498796a36a 100644 --- a/pype/hosts/blender/__init__.py +++ b/pype/hosts/blender/__init__.py @@ -27,6 +27,7 @@ def install(): avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH)) avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH)) + avalon.on("new", on_new) avalon.on("open", on_open) @@ -38,8 +39,7 @@ def uninstall(): avalon.deregister_plugin_path(avalon.Creator, str(CREATE_PATH)) -def on_open(arg1, arg2): - +def set_start_end_frames(): from avalon import io asset_name = io.Session["AVALON_ASSET"] @@ -50,3 +50,11 @@ def on_open(arg1, arg2): bpy.context.scene.frame_start = asset_doc["data"]["frameStart"] bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"] + + +def on_new(arg1, arg2): + set_start_end_frames() + + +def on_open(arg1, arg2): + set_start_end_frames() From 034e4237fe392f040b10c4c7c83957d3439b77a0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 16:11:00 +0200 Subject: [PATCH 48/69] removed duplicated function --- pype/modules/ftrack/lib/custom_db_connector.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pype/modules/ftrack/lib/custom_db_connector.py b/pype/modules/ftrack/lib/custom_db_connector.py index a734b3f80a..a55c0b15dd 100644 --- a/pype/modules/ftrack/lib/custom_db_connector.py +++ b/pype/modules/ftrack/lib/custom_db_connector.py @@ -49,17 +49,6 @@ def check_active_table(func): return decorated -def check_active_table(func): - """Handling auto reconnect in 3 retry times""" - @functools.wraps(func) - def decorated(obj, *args, **kwargs): - if not obj.active_table: - raise NotActiveTable("Active table is not set. (This is bug)") - return func(obj, *args, **kwargs) - - return decorated - - class DbConnector: log = logging.getLogger(__name__) timeout = 1000 From 4f0f943d45e44c894dcd01050058e4907caf1832 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 16:11:50 +0200 Subject: [PATCH 49/69] make `database_name` as required argument in CustomDbConnector --- pype/modules/ftrack/lib/custom_db_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/custom_db_connector.py b/pype/modules/ftrack/lib/custom_db_connector.py index a55c0b15dd..60469159df 100644 --- a/pype/modules/ftrack/lib/custom_db_connector.py +++ b/pype/modules/ftrack/lib/custom_db_connector.py @@ -54,7 +54,7 @@ class DbConnector: timeout = 1000 def __init__( - self, uri, port=None, database_name=None, table_name=None + self, uri, database_name, port=None, table_name=None ): self._mongo_client = None self._sentry_client = None From 748da390a5c3360d0048f2b3c2619cd6cc381334 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 16:12:21 +0200 Subject: [PATCH 50/69] removed check of database_name in components --- pype/modules/ftrack/lib/custom_db_connector.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pype/modules/ftrack/lib/custom_db_connector.py b/pype/modules/ftrack/lib/custom_db_connector.py index 60469159df..05dfc0febb 100644 --- a/pype/modules/ftrack/lib/custom_db_connector.py +++ b/pype/modules/ftrack/lib/custom_db_connector.py @@ -67,9 +67,6 @@ class DbConnector: if port is None: port = components.get("port") - if database_name is None: - database_name = components.get("database") - if database_name is None: raise ValueError( "Database is not defined for connection. {}".format(uri) From a7196bce9e7773a411f89ad86bc8ddc11dccb52f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 16:16:10 +0200 Subject: [PATCH 51/69] renamed class `DbConnector` to `CustomDbConnector` in `custom_db_connector` and fixed argument order --- pype/modules/ftrack/ftrack_server/lib.py | 6 +++--- pype/modules/ftrack/ftrack_server/sub_event_storer.py | 4 ++-- pype/modules/ftrack/lib/custom_db_connector.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index 8377187ebe..bd02daebb1 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -26,7 +26,7 @@ from pype.api import ( compose_url ) -from pype.modules.ftrack.lib.custom_db_connector import DbConnector +from pype.modules.ftrack.lib.custom_db_connector import CustomDbConnector TOPIC_STATUS_SERVER = "pype.event.server.status" @@ -166,10 +166,10 @@ class ProcessEventHub(SocketBaseEventHub): pypelog = Logger().get_logger("Session Processor") def __init__(self, *args, **kwargs): - self.dbcon = DbConnector( + self.dbcon = CustomDbConnector( self.uri, - self.port, self.database, + self.port, self.table_name ) super(ProcessEventHub, self).__init__(*args, **kwargs) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_storer.py b/pype/modules/ftrack/ftrack_server/sub_event_storer.py index 61b9aaf2c8..1635f6cea3 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_storer.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_storer.py @@ -12,7 +12,7 @@ from pype.modules.ftrack.ftrack_server.lib import ( get_ftrack_event_mongo_info, TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT ) -from pype.modules.ftrack.lib.custom_db_connector import DbConnector +from pype.modules.ftrack.lib.custom_db_connector import CustomDbConnector from pype.api import Logger log = Logger().get_logger("Event storer") @@ -24,7 +24,7 @@ class SessionFactory: uri, port, database, table_name = get_ftrack_event_mongo_info() -dbcon = DbConnector(uri, port, database, table_name) +dbcon = CustomDbConnector(uri, database, port, table_name) # ignore_topics = ["ftrack.meta.connected"] ignore_topics = [] diff --git a/pype/modules/ftrack/lib/custom_db_connector.py b/pype/modules/ftrack/lib/custom_db_connector.py index 05dfc0febb..a93eaeb08b 100644 --- a/pype/modules/ftrack/lib/custom_db_connector.py +++ b/pype/modules/ftrack/lib/custom_db_connector.py @@ -40,7 +40,7 @@ def auto_reconnect(func): def check_active_table(func): - """Check if DbConnector has active table before db method is called""" + """Check if CustomDbConnector has active collection.""" @functools.wraps(func) def decorated(obj, *args, **kwargs): if not obj.active_table: @@ -49,7 +49,7 @@ def check_active_table(func): return decorated -class DbConnector: +class CustomDbConnector: log = logging.getLogger(__name__) timeout = 1000 @@ -85,7 +85,7 @@ class DbConnector: # not all methods of PyMongo database are implemented with this it is # possible to use them too try: - return super(DbConnector, self).__getattribute__(attr) + return super(CustomDbConnector, self).__getattribute__(attr) except AttributeError: if self.active_table is None: raise NotActiveTable() From 7dba84dea1ad8754437d8de9b4f93549f4781cd6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 16:19:24 +0200 Subject: [PATCH 52/69] do not compose mongo url for ftrack events with database name and collection --- pype/modules/ftrack/ftrack_server/lib.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index bd02daebb1..acf31ab437 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -44,15 +44,8 @@ def get_ftrack_event_mongo_info(): mongo_url = os.environ.get("FTRACK_EVENTS_MONGO_URL") if mongo_url is not None: components = decompose_url(mongo_url) - _used_ftrack_url = True else: components = get_default_components() - _used_ftrack_url = False - - if not _used_ftrack_url or components["database"] is None: - components["database"] = database_name - - components.pop("collection", None) uri = compose_url(**components) From 1a746aa2804174b1066f4a9d1a761f7e9b00b748 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 16:40:02 +0200 Subject: [PATCH 53/69] dont use None as default value when looking for matching template key --- pype/plugins/global/publish/integrate_new.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 040ed9cd67..0ec8045448 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -727,7 +727,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): task_name = io.Session.get("AVALON_TASK") family = self.main_family_from_instance(instance) - matching_profiles = None + matching_profiles = {} highest_value = -1 self.log.info(self.template_name_profiles) for name, filters in self.template_name_profiles.items(): @@ -745,7 +745,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): value += 1 if value > highest_value: - matching_profiles = {} highest_value = value if value == highest_value: From e7f284050b8b8b1297c9b0acf23f1dbd3cacd164 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:41:09 +0200 Subject: [PATCH 54/69] IdleManager is not QThread but threading.Thread --- pype/modules/idle_manager/idle_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/modules/idle_manager/idle_manager.py b/pype/modules/idle_manager/idle_manager.py index cfcdfef78f..80776a0541 100644 --- a/pype/modules/idle_manager/idle_manager.py +++ b/pype/modules/idle_manager/idle_manager.py @@ -1,11 +1,12 @@ import time import collections from Qt import QtCore +import threading from pynput import mouse, keyboard from pype.api import Logger -class IdleManager(QtCore.QThread): +class IdleManager(threading.Thread): """ Measure user's idle time in seconds. Idle time resets on keyboard/mouse input. Is able to emit signals at specific time idle. From b28c7f36fb03011a757f06aac91c44d92d9011c9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:42:23 +0200 Subject: [PATCH 55/69] there are not registered Qt signals ut callbacks --- pype/modules/idle_manager/idle_manager.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pype/modules/idle_manager/idle_manager.py b/pype/modules/idle_manager/idle_manager.py index 80776a0541..66f63fbace 100644 --- a/pype/modules/idle_manager/idle_manager.py +++ b/pype/modules/idle_manager/idle_manager.py @@ -11,14 +11,13 @@ class IdleManager(threading.Thread): Idle time resets on keyboard/mouse input. Is able to emit signals at specific time idle. """ - time_signals = collections.defaultdict(list) + time_callbacks = collections.defaultdict(list) idle_time = 0 signal_reset_timer = QtCore.Signal() def __init__(self): super(IdleManager, self).__init__() self.log = Logger().get_logger(self.__class__.__name__) - self.signal_reset_timer.connect(self._reset_time) self.qaction = None self.failed_icon = None self._is_running = False @@ -33,18 +32,18 @@ class IdleManager(threading.Thread): def tray_exit(self): self.stop() try: - self.time_signals = {} + self.time_callbacks = {} except Exception: pass - def add_time_signal(self, emit_time, signal): - """ If any module want to use IdleManager, need to use add_time_signal - :param emit_time: time when signal will be emitted - :type emit_time: int - :param signal: signal that will be emitted (without objects) - :type signal: QtCore.Signal + def add_time_callback(self, emit_time, callback): + """If any module want to use IdleManager, need to use this method. + + Args: + emit_time(int): Time when callback will be triggered. + callback(func): Callback that will be triggered. """ - self.time_signals[emit_time].append(signal) + self.time_callbacks[emit_time].append(callback) @property def is_running(self): From 3660ca7c10071d1244f6e41ff0d303ba4a2fb195 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:43:48 +0200 Subject: [PATCH 56/69] mouse and keyboard threads are not QThreads --- pype/modules/idle_manager/idle_manager.py | 63 +++++++---------------- 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/pype/modules/idle_manager/idle_manager.py b/pype/modules/idle_manager/idle_manager.py index 66f63fbace..f952434546 100644 --- a/pype/modules/idle_manager/idle_manager.py +++ b/pype/modules/idle_manager/idle_manager.py @@ -58,9 +58,9 @@ class IdleManager(threading.Thread): def run(self): self.log.info('IdleManager has started') self._is_running = True - thread_mouse = MouseThread(self.signal_reset_timer) + thread_mouse = MouseThread(self._reset_time) thread_mouse.start() - thread_keyboard = KeyboardThread(self.signal_reset_timer) + thread_keyboard = KeyboardThread(self._reset_time) thread_keyboard.start() try: while self.is_running: @@ -79,16 +79,14 @@ class IdleManager(threading.Thread): # Threads don't have their attrs when Qt application already finished try: - thread_mouse.signal_stop.emit() - thread_mouse.terminate() - thread_mouse.wait() + thread_mouse.stop() + thread_mouse.join() except AttributeError: pass try: - thread_keyboard.signal_stop.emit() - thread_keyboard.terminate() - thread_keyboard.wait() + thread_keyboard.stop() + thread_keyboard.join() except AttributeError: pass @@ -96,49 +94,24 @@ class IdleManager(threading.Thread): self.log.info('IdleManager has stopped') -class MouseThread(QtCore.QThread): - """Listens user's mouse movement - """ - signal_stop = QtCore.Signal() +class MouseThread(mouse.Listener): + """Listens user's mouse movement.""" - def __init__(self, signal): - super(MouseThread, self).__init__() - self.signal_stop.connect(self.stop) - self.m_listener = None - - self.signal_reset_timer = signal - - def stop(self): - if self.m_listener is not None: - self.m_listener.stop() + def __init__(self, callback): + super(MouseThread, self).__init__(on_move=self.on_move) + self.callback = callback def on_move(self, posx, posy): - self.signal_reset_timer.emit() - - def run(self): - self.m_listener = mouse.Listener(on_move=self.on_move) - self.m_listener.start() + self.callback() -class KeyboardThread(QtCore.QThread): - """Listens user's keyboard input - """ - signal_stop = QtCore.Signal() +class KeyboardThread(keyboard.Listener): + """Listens user's keyboard input.""" - def __init__(self, signal): - super(KeyboardThread, self).__init__() - self.signal_stop.connect(self.stop) - self.k_listener = None + def __init__(self, callback): + super(KeyboardThread, self).__init__(on_press=self.on_press) - self.signal_reset_timer = signal - - def stop(self): - if self.k_listener is not None: - self.k_listener.stop() + self.callback = callback def on_press(self, key): - self.signal_reset_timer.emit() - - def run(self): - self.k_listener = keyboard.Listener(on_press=self.on_press) - self.k_listener.start() + self.callback() From b15af67bdb96d6bc5bf14828f42503e1a5fe2c90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:44:49 +0200 Subject: [PATCH 57/69] removed rest of Qt from idle manager --- pype/modules/idle_manager/idle_manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/modules/idle_manager/idle_manager.py b/pype/modules/idle_manager/idle_manager.py index f952434546..15618d6076 100644 --- a/pype/modules/idle_manager/idle_manager.py +++ b/pype/modules/idle_manager/idle_manager.py @@ -1,6 +1,5 @@ import time import collections -from Qt import QtCore import threading from pynput import mouse, keyboard from pype.api import Logger @@ -13,7 +12,6 @@ class IdleManager(threading.Thread): """ time_callbacks = collections.defaultdict(list) idle_time = 0 - signal_reset_timer = QtCore.Signal() def __init__(self): super(IdleManager, self).__init__() From adb9749d63ef5c208e2f57aecd0022359b47d6ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:45:09 +0200 Subject: [PATCH 58/69] callback are executed in threads which are joined when done --- pype/modules/idle_manager/idle_manager.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pype/modules/idle_manager/idle_manager.py b/pype/modules/idle_manager/idle_manager.py index 15618d6076..3a9f9154a9 100644 --- a/pype/modules/idle_manager/idle_manager.py +++ b/pype/modules/idle_manager/idle_manager.py @@ -19,6 +19,7 @@ class IdleManager(threading.Thread): self.qaction = None self.failed_icon = None self._is_running = False + self.threads = [] def set_qaction(self, qaction, failed_icon): self.qaction = qaction @@ -62,11 +63,20 @@ class IdleManager(threading.Thread): thread_keyboard.start() try: while self.is_running: + if self.idle_time in self.time_callbacks: + for callback in self.time_callbacks[self.idle_time]: + thread = threading.Thread(target=callback) + thread.start() + self.threads.append(thread) + + for thread in tuple(self.threads): + if not thread.isAlive(): + thread.join() + self.threads.remove(thread) + self.idle_time += 1 - if self.idle_time in self.time_signals: - for signal in self.time_signals[self.idle_time]: - signal.emit() time.sleep(1) + except Exception: self.log.warning( 'Idle Manager service has failed', exc_info=True From 66190c01854d1b5cdce204b7b6e1dc7c8210f8bd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:45:52 +0200 Subject: [PATCH 59/69] SignalHandler moved from timers_manager to widget part --- pype/modules/timers_manager/timers_manager.py | 14 +------------- pype/modules/timers_manager/widget_user_idle.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pype/modules/timers_manager/timers_manager.py b/pype/modules/timers_manager/timers_manager.py index cec730d007..55bec8d963 100644 --- a/pype/modules/timers_manager/timers_manager.py +++ b/pype/modules/timers_manager/timers_manager.py @@ -1,5 +1,4 @@ -from Qt import QtCore -from .widget_user_idle import WidgetUserIdle +from .widget_user_idle import WidgetUserIdle, SignalHandler from pype.api import Logger, config @@ -174,14 +173,3 @@ class TimersManager(metaclass=Singleton): return if self.widget_user_idle.bool_is_showed is False: self.widget_user_idle.show() - - -class SignalHandler(QtCore.QObject): - signal_show_message = QtCore.Signal() - signal_change_label = QtCore.Signal() - signal_stop_timers = QtCore.Signal() - def __init__(self, cls): - super().__init__() - self.signal_show_message.connect(cls.show_message) - self.signal_change_label.connect(cls.change_label) - self.signal_stop_timers.connect(cls.stop_timers) diff --git a/pype/modules/timers_manager/widget_user_idle.py b/pype/modules/timers_manager/widget_user_idle.py index 697c0a04d9..f6f3c49357 100644 --- a/pype/modules/timers_manager/widget_user_idle.py +++ b/pype/modules/timers_manager/widget_user_idle.py @@ -154,3 +154,15 @@ class WidgetUserIdle(QtWidgets.QWidget): def showEvent(self, event): self.bool_is_showed = True + + +class SignalHandler(QtCore.QObject): + signal_show_message = QtCore.Signal() + signal_change_label = QtCore.Signal() + signal_stop_timers = QtCore.Signal() + + def __init__(self, cls): + super().__init__() + self.signal_show_message.connect(cls.show_message) + self.signal_change_label.connect(cls.change_label) + self.signal_stop_timers.connect(cls.stop_timers) From 14d8d3c6a88951957eaa29a39e3989533177d087 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:46:26 +0200 Subject: [PATCH 60/69] simplified user widget in timers manager --- pype/modules/timers_manager/widget_user_idle.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pype/modules/timers_manager/widget_user_idle.py b/pype/modules/timers_manager/widget_user_idle.py index f6f3c49357..22455846fd 100644 --- a/pype/modules/timers_manager/widget_user_idle.py +++ b/pype/modules/timers_manager/widget_user_idle.py @@ -1,4 +1,3 @@ -from pype.api import Logger from avalon import style from Qt import QtCore, QtGui, QtWidgets @@ -8,18 +7,18 @@ class WidgetUserIdle(QtWidgets.QWidget): SIZE_W = 300 SIZE_H = 160 - def __init__(self, parent): + def __init__(self, module, tray_widget): super(WidgetUserIdle, self).__init__() self.bool_is_showed = False self.bool_not_stopped = True - self.parent_widget = parent - self.setWindowIcon(parent.tray_widget.icon) + self.module = module + self.setWindowIcon(tray_widget.icon) self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint | - QtCore.Qt.WindowMinimizeButtonHint + QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowMinimizeButtonHint ) self._translate = QtCore.QCoreApplication.translate @@ -129,11 +128,11 @@ class WidgetUserIdle(QtWidgets.QWidget): self.lbl_rest_time.setText(str_time) def stop_timer(self): - self.parent_widget.stop_timers() + self.module.stop_timers() self.close_widget() def restart_timer(self): - self.parent_widget.restart_timers() + self.module.restart_timers() self.close_widget() def continue_timer(self): From 31a0bfef1805f89add37c015f9a99791fd19384e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 1 Jul 2020 17:46:57 +0200 Subject: [PATCH 61/69] timers manager does not register signals but callbacks in idle manager --- pype/modules/timers_manager/timers_manager.py | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/pype/modules/timers_manager/timers_manager.py b/pype/modules/timers_manager/timers_manager.py index 55bec8d963..8df7952baf 100644 --- a/pype/modules/timers_manager/timers_manager.py +++ b/pype/modules/timers_manager/timers_manager.py @@ -30,7 +30,10 @@ class TimersManager(metaclass=Singleton): self.log = Logger().get_logger(self.__class__.__name__) self.tray_widget = tray_widget self.main_widget = main_widget - self.widget_user_idle = WidgetUserIdle(self) + + self.idle_man = None + self.signal_handler = None + self.widget_user_idle = WidgetUserIdle(self, tray_widget) def set_signal_times(self): try: @@ -113,49 +116,59 @@ class TimersManager(metaclass=Singleton): :param modules: All imported modules from TrayManager :type modules: dict """ - self.s_handler = SignalHandler(self) if 'IdleManager' in modules: + self.signal_handler = SignalHandler(self) if self.set_signal_times() is True: self.register_to_idle_manager(modules['IdleManager']) + def time_callback(self, int_def): + if not self.signal_handler: + return + + if int_def == 0: + self.signal_handler.signal_show_message.emit() + elif int_def == 1: + self.signal_handler.signal_change_label.emit() + elif int_def == 2: + self.signal_handler.signal_stop_timers.emit() + def register_to_idle_manager(self, man_obj): self.idle_man = man_obj + + # Time when message is shown + self.idle_man.add_time_callback( + self.time_show_message, + lambda: self.time_callback(0) + ) + # Times when idle is between show widget and stop timers show_to_stop_range = range( - self.time_show_message-1, self.time_stop_timer + self.time_show_message - 1, self.time_stop_timer ) for num in show_to_stop_range: - self.idle_man.add_time_signal( - num, - self.s_handler.signal_change_label + self.idle_man.add_time_callback( + num, lambda: self.time_callback(1) ) # Times when widget is already shown and user restart idle shown_and_moved_range = range( self.time_stop_timer - self.time_show_message ) for num in shown_and_moved_range: - self.idle_man.add_time_signal( - num, - self.s_handler.signal_change_label + self.idle_man.add_time_callback( + num, lambda: self.time_callback(1) ) - # Time when message is shown - self.idle_man.add_time_signal( - self.time_show_message, - self.s_handler.signal_show_message - ) + # Time when timers are stopped - self.idle_man.add_time_signal( + self.idle_man.add_time_callback( self.time_stop_timer, - self.s_handler.signal_stop_timers + lambda: self.time_callback(2) ) def change_label(self): if self.is_running is False: return - if self.widget_user_idle.bool_is_showed is False: - return - if not hasattr(self, 'idle_man'): + if not self.idle_man or self.widget_user_idle.bool_is_showed is False: return if self.idle_man.idle_time > self.time_show_message: From 2b58592d123695032887f2406fd0b285cfbc3f7e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Jul 2020 10:32:38 +0100 Subject: [PATCH 62/69] Supporting double digits namespace names --- pype/hosts/blender/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index b0420fddfc..f0791c8eb6 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -16,7 +16,7 @@ def asset_name( """Return a consistent name for an asset.""" name = f"{asset}_{subset}" if namespace: - name = f"{namespace}:{name}" + name = f"{namespace:0>2}:{name}" return name From 57f795f4c5f5a4b4f0b121d67fc9ddc5c444f243 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Jul 2020 11:40:30 +0100 Subject: [PATCH 63/69] Support publishing a subset of shots Currently the sequence start frame is hardcoded to 10:00:00:00, so can be expanded later to ask the user for this information. EDLs do not have any data about the sequence start frame. --- .../standalonepublisher/publish/collect_shots.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_shots.py b/pype/plugins/standalonepublisher/publish/collect_shots.py index 853ba4e8de..4f682bd808 100644 --- a/pype/plugins/standalonepublisher/publish/collect_shots.py +++ b/pype/plugins/standalonepublisher/publish/collect_shots.py @@ -56,12 +56,18 @@ class CollectShots(pyblish.api.InstancePlugin): asset_entity = instance.context.data["assetEntity"] asset_name = asset_entity["name"] + # Ask user for sequence start. Usually 10:00:00:00. + sequence_start_frame = 900000 + # Project specific prefix naming. This needs to be replaced with some # options to be more flexible. asset_name = asset_name.split("_")[0] instances = [] for track in tracks: + track_start_frame = ( + abs(track.source_range.start_time.value) - sequence_start_frame + ) for child in track.each_child(): # Transitions are ignored, because Clips have the full frame @@ -69,12 +75,17 @@ class CollectShots(pyblish.api.InstancePlugin): if isinstance(child, otio.schema.transition.Transition): continue + if child.name is None: + continue + # Hardcoded to expect a shot name of "[name].[extension]" child_name = os.path.splitext(child.name)[0].lower() name = f"{asset_name}_{child_name}" - frame_start = child.range_in_parent().start_time.value - frame_end = child.range_in_parent().end_time_inclusive().value + frame_start = track_start_frame + frame_start += child.range_in_parent().start_time.value + frame_end = track_start_frame + frame_end += child.range_in_parent().end_time_inclusive().value label = f"{name} (framerange: {frame_start}-{frame_end})" instances.append( From 72b685fa9be53f71666b30fc34c23b56fff1771f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 2 Jul 2020 15:44:13 +0200 Subject: [PATCH 64/69] stop appending of tools env to existing env --- pype/modules/ftrack/lib/ftrack_app_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 00bd13fd73..531ef310e7 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -213,7 +213,6 @@ class AppAction(BaseAction): tools_env = acre.get_tools(tools_attr) env = acre.compute(tools_env) env = acre.merge(env, current_env=dict(prep_env)) - env = acre.append(dict(prep_env), env) # Get path to execute st_temp_path = os.environ["PYPE_CONFIG"] From 345601a79f9850ca83c47a890e1e5046fc7331af Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Jul 2020 15:23:20 +0100 Subject: [PATCH 65/69] Changed namespace formulation --- pype/hosts/blender/plugin.py | 15 ++++++++------- pype/plugins/blender/load/load_layout.py | 6 ++++-- pype/plugins/blender/load/load_model.py | 6 ++++-- pype/plugins/blender/load/load_rig.py | 6 ++++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index f0791c8eb6..33cccd7d5e 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -14,16 +14,17 @@ def asset_name( asset: str, subset: str, namespace: Optional[str] = None ) -> str: """Return a consistent name for an asset.""" - name = f"{asset}_{subset}" + name = f"{asset}" if namespace: - name = f"{namespace:0>2}:{name}" + name = f"{name}_{namespace}" + name = f"{name}_{subset}" return name -def asset_namespace( +def get_unique_number( asset: str, subset: str ) -> str: - """Return a unique namespace based on the asset name.""" + """Return a unique number based on the asset name.""" avalon_containers = [ c for c in bpy.data.collections if c.name == 'AVALON_CONTAINERS' @@ -35,11 +36,11 @@ def asset_namespace( c.name for c in loaded_assets ] count = 1 - name = f"{asset_name(asset, subset, str(count))}_CON" + name = f"{asset}_{count:0>2}_{subset}_CON" while name in collections_names: count += 1 - name = f"{asset_name(asset, subset, str(count))}_CON" - return str(count) + name = f"{asset}_{count:0>2}_{subset}_CON" + return f"{count:0>2}" def prepare_data(data, container_name): diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index e9e128bb7c..cfab5a207b 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -118,14 +118,16 @@ class BlendLayoutLoader(plugin.AssetLoader): lib_container = plugin.asset_name( asset, subset ) - namespace = namespace or plugin.asset_namespace( + unique_number = plugin.get_unique_number( asset, subset ) + namespace = namespace or f"{asset}_{unique_number}" container_name = plugin.asset_name( - asset, subset, namespace + asset, subset, unique_number ) container = bpy.data.collections.new(lib_container) + container.name = container_name blender.pipeline.containerise_existing( container, name, diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index 0013ccb90a..ad9137a15d 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -90,14 +90,16 @@ class BlendModelLoader(plugin.AssetLoader): lib_container = plugin.asset_name( asset, subset ) - namespace = namespace or plugin.asset_namespace( + unique_number = plugin.get_unique_number( asset, subset ) + namespace = namespace or f"{asset}_{unique_number}" container_name = plugin.asset_name( - asset, subset, namespace + asset, subset, unique_number ) container = bpy.data.collections.new(lib_container) + container.name = container_name blender.pipeline.containerise_existing( container, name, diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index d9e4495090..e09a9cb92f 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -106,14 +106,16 @@ class BlendRigLoader(plugin.AssetLoader): lib_container = plugin.asset_name( asset, subset ) - namespace = namespace or plugin.asset_namespace( + unique_number = plugin.get_unique_number( asset, subset ) + namespace = namespace or f"{asset}_{unique_number}" container_name = plugin.asset_name( - asset, subset, namespace + asset, subset, unique_number ) container = bpy.data.collections.new(lib_container) + container.name = container_name blender.pipeline.containerise_existing( container, name, From 875962ed9b2440272bd95775650f13bcccdc364b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 3 Jul 2020 09:27:43 +0100 Subject: [PATCH 66/69] Hound fixes --- pype/hosts/blender/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index 33cccd7d5e..ab53d49041 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -5,7 +5,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api, blender +from avalon import api VALID_EXTENSIONS = [".blend"] @@ -26,7 +26,7 @@ def get_unique_number( ) -> str: """Return a unique number based on the asset name.""" avalon_containers = [ - c for c in bpy.data.collections + c for c in bpy.data.collections if c.name == 'AVALON_CONTAINERS' ] loaded_assets = [] From b38150173a22761cc5337977b37db2fca45864e6 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 3 Jul 2020 09:35:29 +0100 Subject: [PATCH 67/69] Hound fixes --- pype/hosts/blender/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/blender/__init__.py b/pype/hosts/blender/__init__.py index 498796a36a..dafeca5107 100644 --- a/pype/hosts/blender/__init__.py +++ b/pype/hosts/blender/__init__.py @@ -2,7 +2,7 @@ import os import sys import traceback -from avalon import api as avalon, pipeline, blender +from avalon import api as avalon from pyblish import api as pyblish import bpy From 4636f64b739a91ac53683be627d5eb4838af7997 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Jul 2020 16:07:24 +0100 Subject: [PATCH 68/69] If camera attributes are connected, we can ignore them. --- pype/plugins/maya/load/load_image_plane.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index e95ea6cd8f..653a8d4128 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -50,8 +50,11 @@ class ImagePlaneLoader(api.Loader): camera = selection[0] - camera.displayResolution.set(1) - camera.farClipPlane.set(image_plane_depth * 10) + try: + camera.displayResolution.set(1) + camera.farClipPlane.set(image_plane_depth * 10) + except RuntimeError: + pass # Create image plane image_plane_transform, image_plane_shape = pc.imagePlane( From 2815201d29be1ba15dd1afafab578da791d571b5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Jul 2020 21:42:34 +0100 Subject: [PATCH 69/69] Fix ValidateNukeWriteKnobs --- ...idate_write_knobs.py => validate_knobs.py} | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) rename pype/plugins/nuke/publish/{validate_write_knobs.py => validate_knobs.py} (61%) diff --git a/pype/plugins/nuke/publish/validate_write_knobs.py b/pype/plugins/nuke/publish/validate_knobs.py similarity index 61% rename from pype/plugins/nuke/publish/validate_write_knobs.py rename to pype/plugins/nuke/publish/validate_knobs.py index 24572bedb3..22f0d344c9 100644 --- a/pype/plugins/nuke/publish/validate_write_knobs.py +++ b/pype/plugins/nuke/publish/validate_knobs.py @@ -4,14 +4,14 @@ import pyblish.api import pype.api -class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin): +class ValidateKnobs(pyblish.api.ContextPlugin): """Ensure knobs are consistent. Knobs to validate and their values comes from the Example for presets in config: "presets/plugins/nuke/publish.json" preset, which needs this structure: - "ValidateNukeWriteKnobs": { + "ValidateKnobs": { "enabled": true, "knobs": { "family": { @@ -22,22 +22,31 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin): """ order = pyblish.api.ValidatorOrder - label = "Validate Write Knobs" + label = "Validate Knobs" hosts = ["nuke"] actions = [pype.api.RepairContextAction] optional = True def process(self, context): - # Check for preset existence. - if not getattr(self, "knobs"): + nuke_presets = context.data["presets"].get("nuke") + + if not nuke_presets: + return + + publish_presets = nuke_presets.get("publish") + + if not publish_presets: + return + + plugin_preset = publish_presets.get("ValidateKnobs") + + if not plugin_preset: return - - self.log.debug("__ self.knobs: {}".format(self.knobs)) invalid = self.get_invalid(context, compute=True) if invalid: raise RuntimeError( - "Found knobs with invalid values: {}".format(invalid) + "Found knobs with invalid values:\n{}".format(invalid) ) @classmethod @@ -51,6 +60,8 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin): @classmethod def get_invalid_knobs(cls, context): invalid_knobs = [] + publish_presets = context.data["presets"]["nuke"]["publish"] + knobs_preset = publish_presets["ValidateKnobs"]["knobs"] for instance in context: # Filter publisable instances. if not instance.data["publish"]: @@ -59,15 +70,15 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin): # Filter families. families = [instance.data["family"]] families += instance.data.get("families", []) - families = list(set(families) & set(cls.knobs.keys())) + families = list(set(families) & set(knobs_preset.keys())) if not families: continue # Get all knobs to validate. knobs = {} for family in families: - for preset in cls.knobs[family]: - knobs.update({preset: cls.knobs[family][preset]}) + for preset in knobs_preset[family]: + knobs.update({preset: knobs_preset[family][preset]}) # Get invalid knobs. nodes = [] @@ -82,16 +93,20 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin): for node in nodes: for knob in node.knobs(): - if knob in knobs.keys(): - expected = knobs[knob] - if node[knob].value() != expected: - invalid_knobs.append( - { - "knob": node[knob], - "expected": expected, - "current": node[knob].value() - } - ) + if knob not in knobs.keys(): + continue + + expected = knobs[knob] + if node[knob].value() != expected: + invalid_knobs.append( + { + "knob": node[knob], + "name": node[knob].name(), + "label": node[knob].label(), + "expected": expected, + "current": node[knob].value() + } + ) context.data["invalid_knobs"] = invalid_knobs return invalid_knobs