From 998154b5a905c9764c49abfdc6cb54dc9d5bd1f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 30 Jan 2022 23:53:36 +0100 Subject: [PATCH 01/99] Implement Hardware Renderer 2.0 support for Render Products --- openpype/hosts/maya/api/lib_renderproducts.py | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index e8e4b9aaef..e879682700 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -77,6 +77,7 @@ IMAGE_PREFIXES = { "arnold": "defaultRenderGlobals.imageFilePrefix", "renderman": "rmanGlobals.imageFileFormat", "redshift": "defaultRenderGlobals.imageFilePrefix", + "mayahardware2": "defaultRenderGlobals.imageFilePrefix" } @@ -154,7 +155,8 @@ def get(layer, render_instance=None): "arnold": RenderProductsArnold, "vray": RenderProductsVray, "redshift": RenderProductsRedshift, - "renderman": RenderProductsRenderman + "renderman": RenderProductsRenderman, + "mayahardware2": RenderProductsMayaHardware }.get(renderer_name.lower(), None) if renderer is None: raise UnsupportedRendererException( @@ -1107,6 +1109,67 @@ class RenderProductsRenderman(ARenderProducts): return new_files +class RenderProductsMayaHardware(ARenderProducts): + """Expected files for MayaHardware renderer.""" + + renderer = "mayahardware2" + + extensions = [ + {"label": "JPEG", "index": 8, "extension": "jpg"}, + {"label": "PNG", "index": 32, "extension": "png"}, + {"label": "EXR(exr)", "index": 40, "extension": "exr"} + ] + + def _get_extension(self, value): + result = None + if isinstance(value, int): + extensions = { + extension["index"]: extension["extension"] + for extension in self.extensions + } + try: + result = extensions[value] + except KeyError: + raise NotImplementedError( + "Could not find extension for {}".format(value) + ) + + if isinstance(value, six.string_types): + extensions = { + extension["label"]: extension["extension"] + for extension in self.extensions + } + try: + result = extensions[value] + except KeyError: + raise NotImplementedError( + "Could not find extension for {}".format(value) + ) + + if not result: + raise NotImplementedError( + "Could not find extension for {}".format(value) + ) + + return result + + def get_render_products(self): + """Get all AOVs. + See Also: + :func:`ARenderProducts.get_render_products()` + """ + ext = self._get_extension( + self._get_attr("defaultRenderGlobals.imageFormat") + ) + + products = [] + for cam in self.get_renderable_cameras(): + product = RenderProduct(productName="beauty", ext=ext, camera=cam) + products.append(product) + + return products + + class AOVError(Exception): """Custom exception for determining AOVs.""" From e5f3d28c17989647ecfe0b406e6e4f75ac02b433 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Apr 2022 15:04:08 +0200 Subject: [PATCH 02/99] OP-3113 - moved plugins into addon Plugins were only for specific customer (and not working because of missing psd-tools). --- .../publish/collect_batch_instances.py | 70 ----- .../publish/extract_bg_for_compositing.py | 243 ----------------- .../plugins/publish/extract_bg_main_groups.py | 248 ------------------ .../publish/extract_images_from_psd.py | 171 ------------ 4 files changed, 732 deletions(-) delete mode 100644 openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py delete mode 100644 openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py delete mode 100644 openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py delete mode 100644 openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py deleted file mode 100644 index 4ca1f72cc4..0000000000 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py +++ /dev/null @@ -1,70 +0,0 @@ -import copy -import pyblish.api -from pprint import pformat - - -class CollectBatchInstances(pyblish.api.InstancePlugin): - """Collect all available instances for batch publish.""" - - label = "Collect Batch Instances" - order = pyblish.api.CollectorOrder + 0.489 - hosts = ["standalonepublisher"] - families = ["background_batch"] - - # presets - default_subset_task = { - "background_batch": "background" - } - subsets = { - "background_batch": { - "backgroundLayout": { - "task": "background", - "family": "backgroundLayout" - }, - "backgroundComp": { - "task": "background", - "family": "backgroundComp" - }, - "workfileBackground": { - "task": "background", - "family": "workfile" - } - } - } - unchecked_by_default = [] - - def process(self, instance): - context = instance.context - asset_name = instance.data["asset"] - family = instance.data["family"] - - default_task_name = self.default_subset_task.get(family) - for subset_name, subset_data in self.subsets[family].items(): - instance_name = f"{asset_name}_{subset_name}" - task_name = subset_data.get("task") or default_task_name - - # create new instance - new_instance = context.create_instance(instance_name) - - # add original instance data except name key - for key, value in instance.data.items(): - if key not in ["name"]: - # Make sure value is copy since value may be object which - # can be shared across all new created objects - new_instance.data[key] = copy.deepcopy(value) - - # add subset data from preset - new_instance.data.update(subset_data) - - new_instance.data["label"] = instance_name - new_instance.data["subset"] = subset_name - new_instance.data["task"] = task_name - - if subset_name in self.unchecked_by_default: - new_instance.data["publish"] = False - - self.log.info(f"Created new instance: {instance_name}") - self.log.debug(f"_ inst_data: {pformat(new_instance.data)}") - - # delete original instance - context.remove(instance) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py deleted file mode 100644 index 9621d70739..0000000000 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py +++ /dev/null @@ -1,243 +0,0 @@ -import os -import json -import copy - -import openpype.api -from openpype.pipeline import legacy_io - -PSDImage = None - - -class ExtractBGForComp(openpype.api.Extractor): - label = "Extract Background for Compositing" - families = ["backgroundComp"] - hosts = ["standalonepublisher"] - - new_instance_family = "background" - - # Presetable - allowed_group_names = [ - "OL", "BG", "MG", "FG", "SB", "UL", "SKY", "Field Guide", "Field_Guide", - "ANIM" - ] - - def process(self, instance): - # Check if python module `psd_tools` is installed - try: - global PSDImage - from psd_tools import PSDImage - except Exception: - raise AssertionError( - "BUG: Python module `psd-tools` is not installed!" - ) - - self.allowed_group_names = [ - name.lower() - for name in self.allowed_group_names - ] - - self.redo_global_plugins(instance) - - repres = instance.data.get("representations") - if not repres: - self.log.info("There are no representations on instance.") - return - - if not instance.data.get("transfers"): - instance.data["transfers"] = [] - - # Prepare staging dir - staging_dir = self.staging_dir(instance) - if not os.path.exists(staging_dir): - os.makedirs(staging_dir) - - for repre in tuple(repres): - # Skip all files without .psd extension - repre_ext = repre["ext"].lower() - if repre_ext.startswith("."): - repre_ext = repre_ext[1:] - - if repre_ext != "psd": - continue - - # Prepare publish dir for transfers - publish_dir = instance.data["publishDir"] - - # Prepare json filepath where extracted metadata are stored - json_filename = "{}.json".format(instance.name) - json_full_path = os.path.join(staging_dir, json_filename) - - self.log.debug(f"`staging_dir` is \"{staging_dir}\"") - - # Prepare new repre data - new_repre = { - "name": "json", - "ext": "json", - "files": json_filename, - "stagingDir": staging_dir - } - - # TODO add check of list - psd_filename = repre["files"] - psd_folder_path = repre["stagingDir"] - psd_filepath = os.path.join(psd_folder_path, psd_filename) - self.log.debug(f"psd_filepath: \"{psd_filepath}\"") - psd_object = PSDImage.open(psd_filepath) - - json_data, transfers = self.export_compositing_images( - psd_object, staging_dir, publish_dir - ) - self.log.info("Json file path: {}".format(json_full_path)) - with open(json_full_path, "w") as json_filestream: - json.dump(json_data, json_filestream, indent=4) - - instance.data["transfers"].extend(transfers) - instance.data["representations"].remove(repre) - instance.data["representations"].append(new_repre) - - def export_compositing_images(self, psd_object, output_dir, publish_dir): - json_data = { - "__schema_version__": 1, - "children": [] - } - transfers = [] - for main_idx, main_layer in enumerate(psd_object): - if ( - not main_layer.is_visible() - or main_layer.name.lower() not in self.allowed_group_names - or not main_layer.is_group - ): - continue - - export_layers = [] - layers_idx = 0 - for layer in main_layer: - # TODO this way may be added also layers next to "ADJ" - if layer.name.lower() == "adj": - for _layer in layer: - export_layers.append((layers_idx, _layer)) - layers_idx += 1 - - else: - export_layers.append((layers_idx, layer)) - layers_idx += 1 - - if not export_layers: - continue - - main_layer_data = { - "index": main_idx, - "name": main_layer.name, - "children": [] - } - - for layer_idx, layer in export_layers: - has_size = layer.width > 0 and layer.height > 0 - if not has_size: - self.log.debug(( - "Skipping layer \"{}\" because does " - "not have any content." - ).format(layer.name)) - continue - - main_layer_name = main_layer.name.replace(" ", "_") - layer_name = layer.name.replace(" ", "_") - - filename = "{:0>2}_{}_{:0>2}_{}.png".format( - main_idx + 1, main_layer_name, layer_idx + 1, layer_name - ) - layer_data = { - "index": layer_idx, - "name": layer.name, - "filename": filename - } - output_filepath = os.path.join(output_dir, filename) - dst_filepath = os.path.join(publish_dir, filename) - transfers.append((output_filepath, dst_filepath)) - - pil_object = layer.composite(viewport=psd_object.viewbox) - pil_object.save(output_filepath, "PNG") - - main_layer_data["children"].append(layer_data) - - if main_layer_data["children"]: - json_data["children"].append(main_layer_data) - - return json_data, transfers - - def redo_global_plugins(self, instance): - # TODO do this in collection phase - # Copy `families` and check if `family` is not in current families - families = instance.data.get("families") or list() - if families: - families = list(set(families)) - - if self.new_instance_family in families: - families.remove(self.new_instance_family) - - self.log.debug( - "Setting new instance families {}".format(str(families)) - ) - instance.data["families"] = families - - # Override instance data with new information - instance.data["family"] = self.new_instance_family - - subset_name = instance.data["anatomyData"]["subset"] - asset_doc = instance.data["assetEntity"] - latest_version = self.find_last_version(subset_name, asset_doc) - version_number = 1 - if latest_version is not None: - version_number += latest_version - - instance.data["latestVersion"] = latest_version - instance.data["version"] = version_number - - # Same data apply to anatomy data - instance.data["anatomyData"].update({ - "family": self.new_instance_family, - "version": version_number - }) - - # Redo publish and resources dir - anatomy = instance.context.data["anatomy"] - template_data = copy.deepcopy(instance.data["anatomyData"]) - template_data.update({ - "frame": "FRAME_TEMP", - "representation": "TEMP" - }) - anatomy_filled = anatomy.format(template_data) - if "folder" in anatomy.templates["publish"]: - publish_folder = anatomy_filled["publish"]["folder"] - else: - publish_folder = os.path.dirname(anatomy_filled["publish"]["path"]) - - publish_folder = os.path.normpath(publish_folder) - resources_folder = os.path.join(publish_folder, "resources") - - instance.data["publishDir"] = publish_folder - instance.data["resourcesDir"] = resources_folder - - self.log.debug("publishDir: \"{}\"".format(publish_folder)) - self.log.debug("resourcesDir: \"{}\"".format(resources_folder)) - - def find_last_version(self, subset_name, asset_doc): - subset_doc = legacy_io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset_doc["_id"] - }) - - if subset_doc is None: - self.log.debug("Subset entity does not exist yet.") - else: - version_doc = legacy_io.find_one( - { - "type": "version", - "parent": subset_doc["_id"] - }, - sort=[("name", -1)] - ) - if version_doc: - return int(version_doc["name"]) - return None diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py deleted file mode 100644 index b45f04e574..0000000000 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py +++ /dev/null @@ -1,248 +0,0 @@ -import os -import copy -import json - -import pyblish.api - -import openpype.api -from openpype.pipeline import legacy_io - -PSDImage = None - - -class ExtractBGMainGroups(openpype.api.Extractor): - label = "Extract Background Layout" - order = pyblish.api.ExtractorOrder + 0.02 - families = ["backgroundLayout"] - hosts = ["standalonepublisher"] - - new_instance_family = "background" - - # Presetable - allowed_group_names = [ - "OL", "BG", "MG", "FG", "UL", "SB", "SKY", "Field Guide", "Field_Guide", - "ANIM" - ] - - def process(self, instance): - # Check if python module `psd_tools` is installed - try: - global PSDImage - from psd_tools import PSDImage - except Exception: - raise AssertionError( - "BUG: Python module `psd-tools` is not installed!" - ) - - self.allowed_group_names = [ - name.lower() - for name in self.allowed_group_names - ] - repres = instance.data.get("representations") - if not repres: - self.log.info("There are no representations on instance.") - return - - self.redo_global_plugins(instance) - - repres = instance.data.get("representations") - if not repres: - self.log.info("There are no representations on instance.") - return - - if not instance.data.get("transfers"): - instance.data["transfers"] = [] - - # Prepare staging dir - staging_dir = self.staging_dir(instance) - if not os.path.exists(staging_dir): - os.makedirs(staging_dir) - - # Prepare publish dir for transfers - publish_dir = instance.data["publishDir"] - - for repre in tuple(repres): - # Skip all files without .psd extension - repre_ext = repre["ext"].lower() - if repre_ext.startswith("."): - repre_ext = repre_ext[1:] - - if repre_ext != "psd": - continue - - # Prepare json filepath where extracted metadata are stored - json_filename = "{}.json".format(instance.name) - json_full_path = os.path.join(staging_dir, json_filename) - - self.log.debug(f"`staging_dir` is \"{staging_dir}\"") - - # Prepare new repre data - new_repre = { - "name": "json", - "ext": "json", - "files": json_filename, - "stagingDir": staging_dir - } - - # TODO add check of list - psd_filename = repre["files"] - psd_folder_path = repre["stagingDir"] - psd_filepath = os.path.join(psd_folder_path, psd_filename) - self.log.debug(f"psd_filepath: \"{psd_filepath}\"") - psd_object = PSDImage.open(psd_filepath) - - json_data, transfers = self.export_compositing_images( - psd_object, staging_dir, publish_dir - ) - self.log.info("Json file path: {}".format(json_full_path)) - with open(json_full_path, "w") as json_filestream: - json.dump(json_data, json_filestream, indent=4) - - instance.data["transfers"].extend(transfers) - instance.data["representations"].remove(repre) - instance.data["representations"].append(new_repre) - - def export_compositing_images(self, psd_object, output_dir, publish_dir): - json_data = { - "__schema_version__": 1, - "children": [] - } - output_ext = ".png" - - to_export = [] - for layer_idx, layer in enumerate(psd_object): - layer_name = layer.name.replace(" ", "_") - if ( - not layer.is_visible() - or layer_name.lower() not in self.allowed_group_names - ): - continue - - has_size = layer.width > 0 and layer.height > 0 - if not has_size: - self.log.debug(( - "Skipping layer \"{}\" because does not have any content." - ).format(layer.name)) - continue - - filebase = "{:0>2}_{}".format(layer_idx, layer_name) - if layer_name.lower() == "anim": - if not layer.is_group: - self.log.warning("ANIM layer is not a group layer.") - continue - - children = [] - for anim_idx, anim_layer in enumerate(layer): - anim_layer_name = anim_layer.name.replace(" ", "_") - filename = "{}_{:0>2}_{}{}".format( - filebase, anim_idx, anim_layer_name, output_ext - ) - children.append({ - "index": anim_idx, - "name": anim_layer.name, - "filename": filename - }) - to_export.append((anim_layer, filename)) - - json_data["children"].append({ - "index": layer_idx, - "name": layer.name, - "children": children - }) - continue - - filename = filebase + output_ext - json_data["children"].append({ - "index": layer_idx, - "name": layer.name, - "filename": filename - }) - to_export.append((layer, filename)) - - transfers = [] - for layer, filename in to_export: - output_filepath = os.path.join(output_dir, filename) - dst_filepath = os.path.join(publish_dir, filename) - transfers.append((output_filepath, dst_filepath)) - - pil_object = layer.composite(viewport=psd_object.viewbox) - pil_object.save(output_filepath, "PNG") - - return json_data, transfers - - def redo_global_plugins(self, instance): - # TODO do this in collection phase - # Copy `families` and check if `family` is not in current families - families = instance.data.get("families") or list() - if families: - families = list(set(families)) - - if self.new_instance_family in families: - families.remove(self.new_instance_family) - - self.log.debug( - "Setting new instance families {}".format(str(families)) - ) - instance.data["families"] = families - - # Override instance data with new information - instance.data["family"] = self.new_instance_family - - subset_name = instance.data["anatomyData"]["subset"] - asset_doc = instance.data["assetEntity"] - latest_version = self.find_last_version(subset_name, asset_doc) - version_number = 1 - if latest_version is not None: - version_number += latest_version - - instance.data["latestVersion"] = latest_version - instance.data["version"] = version_number - - # Same data apply to anatomy data - instance.data["anatomyData"].update({ - "family": self.new_instance_family, - "version": version_number - }) - - # Redo publish and resources dir - anatomy = instance.context.data["anatomy"] - template_data = copy.deepcopy(instance.data["anatomyData"]) - template_data.update({ - "frame": "FRAME_TEMP", - "representation": "TEMP" - }) - anatomy_filled = anatomy.format(template_data) - if "folder" in anatomy.templates["publish"]: - publish_folder = anatomy_filled["publish"]["folder"] - else: - publish_folder = os.path.dirname(anatomy_filled["publish"]["path"]) - - publish_folder = os.path.normpath(publish_folder) - resources_folder = os.path.join(publish_folder, "resources") - - instance.data["publishDir"] = publish_folder - instance.data["resourcesDir"] = resources_folder - - self.log.debug("publishDir: \"{}\"".format(publish_folder)) - self.log.debug("resourcesDir: \"{}\"".format(resources_folder)) - - def find_last_version(self, subset_name, asset_doc): - subset_doc = legacy_io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset_doc["_id"] - }) - - if subset_doc is None: - self.log.debug("Subset entity does not exist yet.") - else: - version_doc = legacy_io.find_one( - { - "type": "version", - "parent": subset_doc["_id"] - }, - sort=[("name", -1)] - ) - if version_doc: - return int(version_doc["name"]) - return None diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py deleted file mode 100644 index 8485fa0915..0000000000 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py +++ /dev/null @@ -1,171 +0,0 @@ -import os -import copy -import pyblish.api - -import openpype.api -from openpype.pipeline import legacy_io - -PSDImage = None - - -class ExtractImagesFromPSD(openpype.api.Extractor): - # PLUGIN is not currently enabled because was decided to use different - # approach - enabled = False - active = False - label = "Extract Images from PSD" - order = pyblish.api.ExtractorOrder + 0.02 - families = ["backgroundLayout"] - hosts = ["standalonepublisher"] - - new_instance_family = "image" - ignored_instance_data_keys = ("name", "label", "stagingDir", "version") - # Presetable - allowed_group_names = [ - "OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide", "Field_Guide", - "ANIM" - ] - - def process(self, instance): - # Check if python module `psd_tools` is installed - try: - global PSDImage - from psd_tools import PSDImage - except Exception: - raise AssertionError( - "BUG: Python module `psd-tools` is not installed!" - ) - - self.allowed_group_names = [ - name.lower() - for name in self.allowed_group_names - ] - repres = instance.data.get("representations") - if not repres: - self.log.info("There are no representations on instance.") - return - - for repre in tuple(repres): - # Skip all files without .psd extension - repre_ext = repre["ext"].lower() - if repre_ext.startswith("."): - repre_ext = repre_ext[1:] - - if repre_ext != "psd": - continue - - # TODO add check of list of "files" value - psd_filename = repre["files"] - psd_folder_path = repre["stagingDir"] - psd_filepath = os.path.join(psd_folder_path, psd_filename) - self.log.debug(f"psd_filepath: \"{psd_filepath}\"") - psd_object = PSDImage.open(psd_filepath) - - self.create_new_instances(instance, psd_object) - - # Remove the instance from context - instance.context.remove(instance) - - def create_new_instances(self, instance, psd_object): - asset_doc = instance.data["assetEntity"] - for layer in psd_object: - if ( - not layer.is_visible() - or layer.name.lower() not in self.allowed_group_names - ): - continue - - has_size = layer.width > 0 and layer.height > 0 - if not has_size: - self.log.debug(( - "Skipping layer \"{}\" because does " - "not have any content." - ).format(layer.name)) - continue - - layer_name = layer.name.replace(" ", "_") - instance_name = subset_name = f"image{layer_name}" - self.log.info( - f"Creating new instance with name \"{instance_name}\"" - ) - new_instance = instance.context.create_instance(instance_name) - for key, value in instance.data.items(): - if key not in self.ignored_instance_data_keys: - new_instance.data[key] = copy.deepcopy(value) - - new_instance.data["label"] = " ".join( - (new_instance.data["asset"], instance_name) - ) - - # Find latest version - latest_version = self.find_last_version(subset_name, asset_doc) - version_number = 1 - if latest_version is not None: - version_number += latest_version - - self.log.info( - "Next version of instance \"{}\" will be {}".format( - instance_name, version_number - ) - ) - - # Set family and subset - new_instance.data["family"] = self.new_instance_family - new_instance.data["subset"] = subset_name - new_instance.data["version"] = version_number - new_instance.data["latestVersion"] = latest_version - - new_instance.data["anatomyData"].update({ - "subset": subset_name, - "family": self.new_instance_family, - "version": version_number - }) - - # Copy `families` and check if `family` is not in current families - families = new_instance.data.get("families") or list() - if families: - families = list(set(families)) - - if self.new_instance_family in families: - families.remove(self.new_instance_family) - new_instance.data["families"] = families - - # Prepare staging dir for new instance - staging_dir = self.staging_dir(new_instance) - - output_filename = "{}.png".format(layer_name) - output_filepath = os.path.join(staging_dir, output_filename) - pil_object = layer.composite(viewport=psd_object.viewbox) - pil_object.save(output_filepath, "PNG") - - new_repre = { - "name": "png", - "ext": "png", - "files": output_filename, - "stagingDir": staging_dir - } - self.log.debug( - "Creating new representation: {}".format(new_repre) - ) - new_instance.data["representations"] = [new_repre] - - def find_last_version(self, subset_name, asset_doc): - subset_doc = legacy_io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset_doc["_id"] - }) - - if subset_doc is None: - self.log.debug("Subset entity does not exist yet.") - else: - version_doc = legacy_io.find_one( - { - "type": "version", - "parent": subset_doc["_id"] - }, - sort=[("name", -1)] - ) - if version_doc: - return int(version_doc["name"]) - return None From 604263b22e0cdb5ac6d98150d767732e2a068f98 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Apr 2022 15:06:16 +0200 Subject: [PATCH 03/99] OP-3113 - added Background task to default Task enum --- openpype/settings/defaults/project_anatomy/tasks.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/defaults/project_anatomy/tasks.json b/openpype/settings/defaults/project_anatomy/tasks.json index 74504cc4d7..178f2af639 100644 --- a/openpype/settings/defaults/project_anatomy/tasks.json +++ b/openpype/settings/defaults/project_anatomy/tasks.json @@ -40,5 +40,8 @@ }, "Compositing": { "short_name": "comp" + }, + "Background": { + "short_name": "back" } } \ No newline at end of file From 10fd26e086a2b4f6ede4e1a7fbd901d7fdb34cf3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 14:11:47 +0200 Subject: [PATCH 04/99] moved enable review from creator into publish collector --- openpype/hosts/traypublisher/api/plugin.py | 29 ++++++----------- .../plugins/publish/collect_review_family.py | 31 +++++++++++++++++++ .../publish/collect_simple_instances.py | 9 +++--- .../project_settings/traypublisher.json | 1 - .../schema_project_traypublisher.json | 6 ---- 5 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_review_family.py diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 813641a7d2..603f34ee29 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -2,10 +2,7 @@ from openpype.pipeline import ( Creator, CreatedInstance ) -from openpype.lib import ( - FileDef, - BoolDef, -) +from openpype.lib import FileDef from .pipeline import ( list_instances, @@ -43,7 +40,6 @@ class TrayPublishCreator(Creator): class SettingsCreator(TrayPublishCreator): create_allow_context_change = True - enable_review = False extensions = [] def collect_instances(self): @@ -67,19 +63,15 @@ class SettingsCreator(TrayPublishCreator): self._add_instance_to_context(new_instance) def get_instance_attr_defs(self): - output = [] - - file_def = FileDef( - "filepath", - folders=False, - extensions=self.extensions, - allow_sequences=self.allow_sequences, - label="Filepath", - ) - output.append(file_def) - if self.enable_review: - output.append(BoolDef("review", label="Review")) - return output + return [ + FileDef( + "filepath", + folders=False, + extensions=self.extensions, + allow_sequences=self.allow_sequences, + label="Filepath", + ) + ] @classmethod def from_settings(cls, item_data): @@ -97,7 +89,6 @@ class SettingsCreator(TrayPublishCreator): "icon": item_data["icon"], "description": item_data["description"], "detailed_description": item_data["detailed_description"], - "enable_review": item_data["enable_review"], "extensions": item_data["extensions"], "allow_sequences": item_data["allow_sequences"], "default_variants": item_data["default_variants"] diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py b/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py new file mode 100644 index 0000000000..965e251527 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py @@ -0,0 +1,31 @@ +import pyblish.api +from openpype.lib import BoolDef +from openpype.pipeline import OpenPypePyblishPluginMixin + + +class CollectReviewFamily( + pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin +): + """Add review family.""" + + label = "Collect Review Family" + order = pyblish.api.CollectorOrder - 0.49 + + hosts = ["traypublisher"] + families = [ + "image", + "render", + "plate", + "review" + ] + + def process(self, instance): + values = self.get_attr_values_from_data(instance.data) + if values.get("add_review_family"): + instance.data["families"].append("review") + + @classmethod + def get_attribute_defs(cls): + return [ + BoolDef("add_review_family", label="Review", default=True) + ] diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 5fc66084d6..9facd90a48 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -22,10 +22,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): repres = instance.data["representations"] creator_attributes = instance.data["creator_attributes"] - - if creator_attributes.get("review"): - instance.data["families"].append("review") - filepath_item = creator_attributes["filepath"] self.log.info(filepath_item) filepaths = [ @@ -34,6 +30,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): ] instance.data["sourceFilepaths"] = filepaths + instance.data["stagingDir"] = filepath_item["directory"] filenames = filepath_item["filenames"] ext = os.path.splitext(filenames[0])[-1] @@ -46,3 +43,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "stagingDir": filepath_item["directory"], "files": filenames }) + + self.log.debug("Created Simple Settings instance {}".format( + instance.data + )) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 1b0ad67abb..0b54cfd39e 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -8,7 +8,6 @@ "default_variants": [ "Main" ], - "enable_review": false, "description": "Publish workfile backup", "detailed_description": "", "allow_sequences": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 59c675d411..55c1b7b7d7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -45,12 +45,6 @@ "type": "text" } }, - { - "type": "boolean", - "key": "enable_review", - "label": "Enable review", - "tooltip": "Allow to create review from source file/s.\nFiles must be supported to be able create review." - }, { "type": "separator" }, From 1ea991f4f3d277e611587200bd93c007ae0fd660 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 14:30:33 +0200 Subject: [PATCH 05/99] added right click callback for menu --- .../widgets/attribute_defs/files_widget.py | 69 ++++++++++++++----- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index a3ee370bd3..5fe0302507 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -251,7 +251,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel): class ItemWidget(QtWidgets.QWidget): - split_requested = QtCore.Signal(str) + context_menu_requested = QtCore.Signal(QtCore.QPoint) def __init__( self, item_id, label, pixmap_icon, is_sequence, multivalue, parent=None @@ -316,19 +316,9 @@ class ItemWidget(QtWidgets.QWidget): self._update_btn_size() def _on_actions_clicked(self): - menu = QtWidgets.QMenu(self._split_btn) - - action = QtWidgets.QAction("Split sequence", menu) - action.triggered.connect(self._on_split_sequence) - - menu.addAction(action) - pos = self._split_btn.rect().bottomLeft() point = self._split_btn.mapToGlobal(pos) - menu.popup(point) - - def _on_split_sequence(self): - self.split_requested.emit(self._item_id) + self.context_menu_requested.emit(point) class InViewButton(IconButton): @@ -339,6 +329,7 @@ class FilesView(QtWidgets.QListView): """View showing instances and their groups.""" remove_requested = QtCore.Signal() + context_menu_requested = QtCore.Signal(QtCore.QPoint) def __init__(self, *args, **kwargs): super(FilesView, self).__init__(*args, **kwargs) @@ -347,6 +338,7 @@ class FilesView(QtWidgets.QListView): self.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection ) + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) remove_btn = InViewButton(self) pix_enabled = paint_image_with_color( @@ -361,6 +353,7 @@ class FilesView(QtWidgets.QListView): remove_btn.setEnabled(False) remove_btn.clicked.connect(self._on_remove_clicked) + self.customContextMenuRequested.connect(self._on_context_menu_request) self._remove_btn = remove_btn @@ -397,6 +390,12 @@ class FilesView(QtWidgets.QListView): selected_item_ids.add(instance_id) return selected_item_ids + def has_selected_sequence(self): + for index in self.selectionModel().selectedIndexes(): + if index.data(IS_SEQUENCE_ROLE): + return True + return False + def event(self, event): if event.type() == QtCore.QEvent.KeyPress: if ( @@ -408,6 +407,12 @@ class FilesView(QtWidgets.QListView): return super(FilesView, self).event(event) + def _on_context_menu_request(self, pos): + index = self.indexAt(pos) + if index.isValid(): + point = self.mapToGlobal(pos) + self.context_menu_requested.emit(point) + def _on_selection_change(self): self._remove_btn.setEnabled(self.has_selected_item_ids()) @@ -456,6 +461,9 @@ class FilesWidget(QtWidgets.QFrame): files_proxy_model.rowsInserted.connect(self._on_rows_inserted) files_proxy_model.rowsRemoved.connect(self._on_rows_removed) files_view.remove_requested.connect(self._on_remove_requested) + files_view.context_menu_requested.connect( + self._on_context_menu_requested + ) self._in_set_value = False self._single_item = single_item self._multivalue = False @@ -527,7 +535,9 @@ class FilesWidget(QtWidgets.QFrame): is_sequence, self._multivalue ) - widget.split_requested.connect(self._on_split_request) + widget.context_menu_requested.connect( + self._on_context_menu_requested + ) self._files_view.setIndexWidget(index, widget) self._files_proxy_model.setData( index, widget.sizeHint(), QtCore.Qt.SizeHintRole @@ -559,17 +569,22 @@ class FilesWidget(QtWidgets.QFrame): if not self._in_set_value: self.value_changed.emit() - def _on_split_request(self, item_id): + def _on_split_request(self): if self._multivalue: return - file_item = self._files_model.get_file_item_by_id(item_id) - if not file_item: + item_ids = self._files_view.get_selected_item_ids() + if not item_ids: return - new_items = file_item.split_sequence() - self._remove_item_by_ids([item_id]) - self._add_filepaths(new_items) + for item_id in item_ids: + file_item = self._files_model.get_file_item_by_id(item_id) + if not file_item: + return + + new_items = file_item.split_sequence() + self._add_filepaths(new_items) + self._remove_item_by_ids(item_ids) def _on_remove_requested(self): if self._multivalue: @@ -579,6 +594,22 @@ class FilesWidget(QtWidgets.QFrame): if items_to_delete: self._remove_item_by_ids(items_to_delete) + def _on_context_menu_requested(self, pos): + if self._multivalue: + return + + menu = QtWidgets.QMenu(self._files_view) + + remove_action = QtWidgets.QAction("Remove", menu) + remove_action.triggered.connect(self._on_remove_requested) + menu.addAction(remove_action) + + if self._files_view.has_selected_sequence(): + remove_action = QtWidgets.QAction("Split sequence", menu) + remove_action.triggered.connect(self._on_split_request) + + menu.popup(pos) + def sizeHint(self): # Get size hints of widget and visible widgets result = super(FilesWidget, self).sizeHint() From b74b309de19a2f7de6cb06fc6b6afa1ed4d5a624 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 14:34:12 +0200 Subject: [PATCH 06/99] fixed split sequence action --- openpype/widgets/attribute_defs/files_widget.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 5fe0302507..924dbf7fe5 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -410,7 +410,7 @@ class FilesView(QtWidgets.QListView): def _on_context_menu_request(self, pos): index = self.indexAt(pos) if index.isValid(): - point = self.mapToGlobal(pos) + point = self.viewport().mapToGlobal(pos) self.context_menu_requested.emit(point) def _on_selection_change(self): @@ -600,14 +600,15 @@ class FilesWidget(QtWidgets.QFrame): menu = QtWidgets.QMenu(self._files_view) + if self._files_view.has_selected_sequence(): + split_action = QtWidgets.QAction("Split sequence", menu) + split_action.triggered.connect(self._on_split_request) + menu.addAction(split_action) + remove_action = QtWidgets.QAction("Remove", menu) remove_action.triggered.connect(self._on_remove_requested) menu.addAction(remove_action) - if self._files_view.has_selected_sequence(): - remove_action = QtWidgets.QAction("Split sequence", menu) - remove_action.triggered.connect(self._on_split_request) - menu.popup(pos) def sizeHint(self): From 0647a5dcf294e7a7c88f691c7b5114c49927b5fc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 14:44:25 +0200 Subject: [PATCH 07/99] moved create button to right side --- openpype/tools/publisher/widgets/create_dialog.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 243540f243..dd1e00b79b 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -282,9 +282,6 @@ class CreateDialog(QtWidgets.QDialog): subset_name_input = QtWidgets.QLineEdit(self) subset_name_input.setEnabled(False) - create_btn = QtWidgets.QPushButton("Create", self) - create_btn.setEnabled(False) - form_layout = QtWidgets.QFormLayout() form_layout.addRow("Variant:", variant_widget) form_layout.addRow("Subset:", subset_name_input) @@ -295,7 +292,6 @@ class CreateDialog(QtWidgets.QDialog): mid_layout.addWidget(QtWidgets.QLabel("Choose family:", self)) mid_layout.addWidget(creators_view, 1) mid_layout.addLayout(form_layout, 0) - mid_layout.addWidget(create_btn, 0) # ------------ # --- Creator short info and attr defs --- @@ -313,11 +309,22 @@ class CreateDialog(QtWidgets.QDialog): # Precreate attributes widget pre_create_widget = PreCreateWidget(creator_attrs_widget) + # Create button + create_btn_wrapper = QtWidgets.QWidget(creator_attrs_widget) + create_btn = QtWidgets.QPushButton("Create", create_btn_wrapper) + create_btn.setEnabled(False) + + create_btn_wrap_layout = QtWidgets.QHBoxLayout(create_btn_wrapper) + create_btn_wrap_layout.setContentsMargins(0, 0, 0, 0) + create_btn_wrap_layout.addStretch(1) + create_btn_wrap_layout.addWidget(create_btn, 0) + creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget) creator_attrs_layout.setContentsMargins(0, 0, 0, 0) creator_attrs_layout.addWidget(creator_short_desc_widget, 0) creator_attrs_layout.addWidget(separator_widget, 0) creator_attrs_layout.addWidget(pre_create_widget, 1) + creator_attrs_layout.addWidget(create_btn_wrapper, 0) # ------------------------------------- # --- Detailed information about creator --- From 7924affca56d4f68cc5b88ccc6af0509c169b5c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 14:45:55 +0200 Subject: [PATCH 08/99] make FileDef with vertical label as default --- openpype/lib/attribute_definitions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index bfac9da5ce..4ea691ca08 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -560,11 +560,7 @@ class FileDef(AbtractAttrDef): # Change horizontal label is_label_horizontal = kwargs.get("is_label_horizontal") if is_label_horizontal is None: - if single_item: - is_label_horizontal = True - else: - is_label_horizontal = False - kwargs["is_label_horizontal"] = is_label_horizontal + kwargs["is_label_horizontal"] = False self.single_item = single_item self.folders = folders From 889f17d5ee3ee46dd702da1e6a989bf61556a6ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 15:22:24 +0200 Subject: [PATCH 09/99] wrapped header widgets into single widget --- openpype/tools/utils/assets_widget.py | 55 +++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 3d4efcdd4d..90e688073d 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -589,10 +589,12 @@ class AssetsWidget(QtWidgets.QWidget): view = AssetsView(self) view.setModel(proxy) + header_widget = QtWidgets.QWidget(self) + current_asset_icon = qtawesome.icon( "fa.arrow-down", color=get_default_tools_icon_color() ) - current_asset_btn = QtWidgets.QPushButton(self) + current_asset_btn = QtWidgets.QPushButton(header_widget) current_asset_btn.setIcon(current_asset_icon) current_asset_btn.setToolTip("Go to Asset from current Session") # Hide by default @@ -601,15 +603,16 @@ class AssetsWidget(QtWidgets.QWidget): refresh_icon = qtawesome.icon( "fa.refresh", color=get_default_tools_icon_color() ) - refresh_btn = QtWidgets.QPushButton(self) + refresh_btn = QtWidgets.QPushButton(header_widget) refresh_btn.setIcon(refresh_icon) refresh_btn.setToolTip("Refresh items") - filter_input = PlaceholderLineEdit(self) + filter_input = PlaceholderLineEdit(header_widget) filter_input.setPlaceholderText("Filter assets..") # Header - header_layout = QtWidgets.QHBoxLayout() + header_layout = QtWidgets.QHBoxLayout(header_widget) + header_layout.setContentsMargins(0, 0, 0, 0) header_layout.addWidget(filter_input) header_layout.addWidget(current_asset_btn) header_layout.addWidget(refresh_btn) @@ -617,9 +620,8 @@ class AssetsWidget(QtWidgets.QWidget): # Layout layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(4) - layout.addLayout(header_layout) - layout.addWidget(view) + layout.addWidget(header_widget, 0) + layout.addWidget(view, 1) # Signals/Slots filter_input.textChanged.connect(self._on_filter_text_change) @@ -630,6 +632,8 @@ class AssetsWidget(QtWidgets.QWidget): current_asset_btn.clicked.connect(self._on_current_asset_click) view.doubleClicked.connect(self.double_clicked) + self._header_widget = header_widget + self._filter_input = filter_input self._refresh_btn = refresh_btn self._current_asset_btn = current_asset_btn self._model = model @@ -637,8 +641,40 @@ class AssetsWidget(QtWidgets.QWidget): self._view = view self._last_project_name = None + self._last_btns_height = None + self.model_selection = {} + @property + def header_widget(self): + return self._header_widget + + def _check_btns_height(self): + """Make buttons to have same height as filter input field. + + There is not handled case when buttons are bigger then filter input. + """ + if self._filter_input.height() == self._last_btns_height: + return + + height = self._filter_input.height() + self._last_btns_height = height + + for widget in ( + self._refresh_btn, + self._current_asset_btn + ): + widget.setMinimumHeight(height) + widget.setMaximumHeight(height) + + def resizeEvent(self, event): + super(AssetsWidget, self).resizeEvent(event) + self._check_btns_height() + + def showEvent(self, event): + super(AssetsWidget, self).showEvent(event) + self._check_btns_height() + def _create_source_model(self): model = AssetModel(dbcon=self.dbcon, parent=self) model.refreshed.connect(self._on_model_refresh) @@ -669,6 +705,7 @@ class AssetsWidget(QtWidgets.QWidget): This separation gives ability to override this method and use it in differnt way. """ + self.set_current_session_asset() def set_current_session_asset(self): @@ -681,6 +718,7 @@ class AssetsWidget(QtWidgets.QWidget): Some tools may have their global refresh button or do not support refresh at all. """ + if visible is None: visible = not self._refresh_btn.isVisible() self._refresh_btn.setVisible(visible) @@ -690,6 +728,7 @@ class AssetsWidget(QtWidgets.QWidget): Not all tools support using of current context asset. """ + if visible is None: visible = not self._current_asset_btn.isVisible() self._current_asset_btn.setVisible(visible) @@ -723,6 +762,7 @@ class AssetsWidget(QtWidgets.QWidget): so if you're modifying model keep in mind that this method should be called when refresh is done. """ + self._proxy.sort(0) self._set_loading_state(loading=False, empty=not has_item) self.refreshed.emit() @@ -767,6 +807,7 @@ class SingleSelectAssetsWidget(AssetsWidget): Contain single selection specific api methods. """ + def get_selected_asset_id(self): """Currently selected asset id.""" selection_model = self._view.selectionModel() From b27ba52aaf27772a18e6c948a108925906f14c13 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Apr 2022 15:27:42 +0200 Subject: [PATCH 10/99] nuke: fix regex default --- openpype/settings/defaults/project_anatomy/imageio.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index fedae994bf..09a5d98f46 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -170,7 +170,7 @@ "regexInputs": { "inputs": [ { - "regex": "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]", + "regex": "(beauty).*(?=.exr)", "colorspace": "linear" } ] From b17a71fe1466c84d39b74228c066a0819a9b32c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 15:43:06 +0200 Subject: [PATCH 11/99] simplified btns height in assets widget --- openpype/tools/utils/assets_widget.py | 36 ++++++++------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 90e688073d..d1df1193d2 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -617,6 +617,16 @@ class AssetsWidget(QtWidgets.QWidget): header_layout.addWidget(current_asset_btn) header_layout.addWidget(refresh_btn) + # Make header widgets expand vertically if there is a place + for widget in ( + current_asset_btn, + refresh_btn, + filter_input, + ): + size_policy = widget.sizePolicy() + size_policy.setVerticalPolicy(size_policy.MinimumExpanding) + widget.setSizePolicy(size_policy) + # Layout layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -649,32 +659,6 @@ class AssetsWidget(QtWidgets.QWidget): def header_widget(self): return self._header_widget - def _check_btns_height(self): - """Make buttons to have same height as filter input field. - - There is not handled case when buttons are bigger then filter input. - """ - if self._filter_input.height() == self._last_btns_height: - return - - height = self._filter_input.height() - self._last_btns_height = height - - for widget in ( - self._refresh_btn, - self._current_asset_btn - ): - widget.setMinimumHeight(height) - widget.setMaximumHeight(height) - - def resizeEvent(self, event): - super(AssetsWidget, self).resizeEvent(event) - self._check_btns_height() - - def showEvent(self, event): - super(AssetsWidget, self).showEvent(event) - self._check_btns_height() - def _create_source_model(self): model = AssetModel(dbcon=self.dbcon, parent=self) model.refreshed.connect(self._on_model_refresh) From 7504f273806b59c8192dcf4bf7137340aa04d0ba Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Apr 2022 15:53:58 +0200 Subject: [PATCH 12/99] Nuke: fixing default settings for workfile builder loaders --- openpype/settings/defaults/project_settings/nuke.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index ddf996b5f2..0b03a00187 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -220,11 +220,12 @@ "repre_names": [ "exr", "dpx", - "mov" + "mov", + "mp4", + "h264" ], "loaders": [ - "LoadSequence", - "LoadMov" + "LoadClip" ] } ], From 1bc4d6c66a554b2902372546178ddce1220f8638 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 16:14:50 +0200 Subject: [PATCH 13/99] keep creators view label at same height as asset filtering --- .../tools/publisher/widgets/assets_widget.py | 24 +++++++++++++++++++ .../tools/publisher/widgets/create_dialog.py | 19 ++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index 984da59c77..c4d3cf8b1a 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -15,6 +15,7 @@ from openpype.tools.utils.assets_widget import ( class CreateDialogAssetsWidget(SingleSelectAssetsWidget): current_context_required = QtCore.Signal() + header_height_changed = QtCore.Signal(int) def __init__(self, controller, parent): self._controller = controller @@ -27,6 +28,27 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget): self._last_selection = None self._enabled = None + self._last_filter_height = None + + def _check_header_height(self): + """Catch header height changes. + + Label on top of creaters should have same height so Creators view has + same offset. + """ + height = self.header_widget.height() + if height != self._last_filter_height: + self._last_filter_height = height + self.header_height_changed.emit(height) + + def resizeEvent(self, event): + super(CreateDialogAssetsWidget, self).resizeEvent(event) + self._check_header_height() + + def showEvent(self, event): + super(CreateDialogAssetsWidget, self).showEvent(event) + self._check_header_height() + def _on_current_asset_click(self): self.current_context_required.emit() @@ -71,6 +93,7 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): Uses controller to load asset hierarchy. All asset documents are stored by their parents. """ + def __init__(self, controller): super(AssetsHierarchyModel, self).__init__() self._controller = controller @@ -143,6 +166,7 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel): class AssetsDialog(QtWidgets.QDialog): """Dialog to select asset for a context of instance.""" + def __init__(self, controller, parent): super(AssetsDialog, self).__init__(parent) self.setWindowTitle("Select asset") diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index dd1e00b79b..00a9ac785d 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -255,6 +255,14 @@ class CreateDialog(QtWidgets.QDialog): context_layout.addWidget(tasks_widget, 1) # --- Creators view --- + creators_header_widget = QtWidgets.QWidget(self) + header_label_widget = QtWidgets.QLabel( + "Choose family:", creators_header_widget + ) + creators_header_layout = QtWidgets.QHBoxLayout(creators_header_widget) + creators_header_layout.setContentsMargins(0, 0, 0, 0) + creators_header_layout.addWidget(header_label_widget, 1) + creators_view = QtWidgets.QListView(self) creators_model = QtGui.QStandardItemModel() creators_view.setModel(creators_model) @@ -289,7 +297,7 @@ class CreateDialog(QtWidgets.QDialog): mid_widget = QtWidgets.QWidget(self) mid_layout = QtWidgets.QVBoxLayout(mid_widget) mid_layout.setContentsMargins(0, 0, 0, 0) - mid_layout.addWidget(QtWidgets.QLabel("Choose family:", self)) + mid_layout.addWidget(creators_header_widget, 0) mid_layout.addWidget(creators_view, 1) mid_layout.addLayout(form_layout, 0) # ------------ @@ -362,6 +370,10 @@ class CreateDialog(QtWidgets.QDialog): help_btn.clicked.connect(self._on_help_btn) help_btn.resized.connect(self._on_help_btn_resize) + assets_widget.header_height_changed.connect( + self._on_asset_filter_height_change + ) + create_btn.clicked.connect(self._on_create) variant_widget.resized.connect(self._on_variant_widget_resize) variant_input.returnPressed.connect(self._on_create) @@ -394,6 +406,7 @@ class CreateDialog(QtWidgets.QDialog): self.variant_hints_menu = variant_hints_menu self.variant_hints_group = variant_hints_group + self._creators_header_widget = creators_header_widget self.creators_model = creators_model self.creators_view = creators_view self.create_btn = create_btn @@ -472,6 +485,10 @@ class CreateDialog(QtWidgets.QDialog): def _invalidate_prereq(self): self._prereq_timer.start() + def _on_asset_filter_height_change(self, height): + self._creators_header_widget.setMinimumHeight(height) + self._creators_header_widget.setMaximumHeight(height) + def _on_prereq_timer(self): prereq_available = True creator_btn_tooltips = [] From 48e887f2398a70ee9797599118c934b9dbe523f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 16:39:58 +0200 Subject: [PATCH 14/99] modified description heights --- .../tools/publisher/widgets/create_dialog.py | 78 +++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 00a9ac785d..cc47e9f175 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -114,6 +114,8 @@ class CreateErrorMessageBox(ErrorMessageBox): # TODO add creator identifier/label to details class CreatorShortDescWidget(QtWidgets.QWidget): + height_changed = QtCore.Signal(int) + def __init__(self, parent=None): super(CreatorShortDescWidget, self).__init__(parent=parent) @@ -152,6 +154,22 @@ class CreatorShortDescWidget(QtWidgets.QWidget): self._family_label = family_label self._description_label = description_label + self._last_height = None + + def _check_height_change(self): + height = self.height() + if height != self._last_height: + self._last_height = height + self.height_changed.emit(height) + + def showEvent(self, event): + super(CreatorShortDescWidget, self).showEvent(event) + self._check_height_change() + + def resizeEvent(self, event): + super(CreatorShortDescWidget, self).resizeEvent(event) + self._check_height_change() + def set_plugin(self, plugin=None): if not plugin: self._icon_widget.set_icon_def(None) @@ -309,10 +327,10 @@ class CreateDialog(QtWidgets.QDialog): creator_attrs_widget ) - separator_widget = QtWidgets.QWidget(self) - separator_widget.setObjectName("Separator") - separator_widget.setMinimumHeight(2) - separator_widget.setMaximumHeight(2) + attr_separator_widget = QtWidgets.QWidget(self) + attr_separator_widget.setObjectName("Separator") + attr_separator_widget.setMinimumHeight(2) + attr_separator_widget.setMaximumHeight(2) # Precreate attributes widget pre_create_widget = PreCreateWidget(creator_attrs_widget) @@ -330,21 +348,41 @@ class CreateDialog(QtWidgets.QDialog): creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget) creator_attrs_layout.setContentsMargins(0, 0, 0, 0) creator_attrs_layout.addWidget(creator_short_desc_widget, 0) - creator_attrs_layout.addWidget(separator_widget, 0) + creator_attrs_layout.addWidget(attr_separator_widget, 0) creator_attrs_layout.addWidget(pre_create_widget, 1) creator_attrs_layout.addWidget(create_btn_wrapper, 0) # ------------------------------------- # --- Detailed information about creator --- # Detailed description of creator - detail_description_widget = QtWidgets.QTextEdit(self) - detail_description_widget.setObjectName("InfoText") - detail_description_widget.setTextInteractionFlags( + detail_description_widget = QtWidgets.QWidget(self) + + detail_placoholder_widget = QtWidgets.QWidget( + detail_description_widget + ) + detail_placoholder_widget.setAttribute( + QtCore.Qt.WA_TranslucentBackground + ) + + detail_description_input = QtWidgets.QTextEdit( + detail_description_widget + ) + detail_description_input.setObjectName("InfoText") + detail_description_input.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction ) - detail_description_widget.setVisible(False) - # ------------------------------------------- + detail_description_layout = QtWidgets.QVBoxLayout( + detail_description_widget + ) + detail_description_layout.setContentsMargins(0, 0, 0, 0) + detail_description_layout.setSpacing(0) + detail_description_layout.addWidget(detail_placoholder_widget, 0) + detail_description_layout.addWidget(detail_description_input, 1) + + detail_description_widget.setVisible(False) + + # ------------------------------------------- splitter_widget = QtWidgets.QSplitter(self) splitter_widget.addWidget(context_widget) splitter_widget.addWidget(mid_widget) @@ -359,6 +397,7 @@ class CreateDialog(QtWidgets.QDialog): layout.addWidget(splitter_widget, 1) # Floating help button + # - Create this button as last to be fully visible help_btn = HelpButton(self) prereq_timer = QtCore.QTimer() @@ -388,6 +427,9 @@ class CreateDialog(QtWidgets.QDialog): self._on_current_session_context_request ) tasks_widget.task_changed.connect(self._on_task_change) + creator_short_desc_widget.height_changed.connect( + self._on_description_height_change + ) controller.add_plugins_refresh_callback(self._on_plugins_refresh) @@ -413,7 +455,11 @@ class CreateDialog(QtWidgets.QDialog): self._creator_short_desc_widget = creator_short_desc_widget self._pre_create_widget = pre_create_widget + self._attr_separator_widget = attr_separator_widget + + self._detail_placoholder_widget = detail_placoholder_widget self._detail_description_widget = detail_description_widget + self._detail_description_input = detail_description_input self._help_btn = help_btn self._prereq_timer = prereq_timer @@ -619,6 +665,12 @@ class CreateDialog(QtWidgets.QDialog): if self._task_name: self._tasks_widget.select_task_name(self._task_name) + def _on_description_height_change(self): + # Use separator's 'y' position as height + height = self._attr_separator_widget.y() + self._detail_placoholder_widget.setMinimumHeight(height) + self._detail_placoholder_widget.setMaximumHeight(height) + def _on_creator_item_change(self, new_index, _old_index): identifier = None if new_index.isValid(): @@ -666,14 +718,14 @@ class CreateDialog(QtWidgets.QDialog): def _set_creator_detailed_text(self, creator): if not creator: - self._detail_description_widget.setPlainText("") + self._detail_description_input.setPlainText("") return detailed_description = creator.get_detail_description() or "" if commonmark: html = commonmark.commonmark(detailed_description) - self._detail_description_widget.setHtml(html) + self._detail_description_input.setHtml(html) else: - self._detail_description_widget.setMarkdown(detailed_description) + self._detail_description_input.setMarkdown(detailed_description) def _set_creator_by_identifier(self, identifier): creator = self.controller.manual_creators.get(identifier) From 542efb57bc5df0a6e966c3fa6ac265bce693a599 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 16:51:19 +0200 Subject: [PATCH 15/99] change style of detailed description --- openpype/style/style.css | 8 ++++++++ openpype/tools/publisher/widgets/create_dialog.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index ae04a433fb..4032732a78 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -856,6 +856,14 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { } /* New Create/Publish UI */ +#CreatorDetailedDescription { + padding-left: 5px; + padding-right: 5px; + padding-top: 5px; + background: transparent; + border: 1px solid {color:border}; +} + #CreateDialogHelpButton { background: rgba(255, 255, 255, 31); border-top-right-radius: 0; diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index cc47e9f175..8e1ab3502d 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -367,7 +367,7 @@ class CreateDialog(QtWidgets.QDialog): detail_description_input = QtWidgets.QTextEdit( detail_description_widget ) - detail_description_input.setObjectName("InfoText") + detail_description_input.setObjectName("CreatorDetailedDescription") detail_description_input.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction ) From 99096269b52db11ad2bb5f9040a221cafbee97d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 19:29:36 +0200 Subject: [PATCH 16/99] change separator size --- openpype/tools/publisher/widgets/create_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 8e1ab3502d..8c4f9d52a1 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -329,8 +329,8 @@ class CreateDialog(QtWidgets.QDialog): attr_separator_widget = QtWidgets.QWidget(self) attr_separator_widget.setObjectName("Separator") - attr_separator_widget.setMinimumHeight(2) - attr_separator_widget.setMaximumHeight(2) + attr_separator_widget.setMinimumHeight(1) + attr_separator_widget.setMaximumHeight(1) # Precreate attributes widget pre_create_widget = PreCreateWidget(creator_attrs_widget) From 833027f163c6a313db9ff26417b39ab3276fb600 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 29 Apr 2022 19:29:58 +0200 Subject: [PATCH 17/99] animate description showing --- .../tools/publisher/widgets/create_dialog.py | 137 ++++++++++++++++-- 1 file changed, 128 insertions(+), 9 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 8c4f9d52a1..10be47c517 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -404,8 +404,13 @@ class CreateDialog(QtWidgets.QDialog): prereq_timer.setInterval(50) prereq_timer.setSingleShot(True) + desc_width_anim_timer = QtCore.QTimer() + desc_width_anim_timer.setInterval(10) + prereq_timer.timeout.connect(self._on_prereq_timer) + desc_width_anim_timer.timeout.connect(self._on_desc_animation) + help_btn.clicked.connect(self._on_help_btn) help_btn.resized.connect(self._on_help_btn_resize) @@ -465,6 +470,15 @@ class CreateDialog(QtWidgets.QDialog): self._prereq_timer = prereq_timer self._first_show = True + # Description animation + self._description_size_policy = detail_description_widget.sizePolicy() + self._desc_width_anim_timer = desc_width_anim_timer + self._desc_widget_step = 0 + self._last_description_width = None + self._last_full_width = 0 + self._expected_description_width = 0 + self._other_widgets_widths = [] + def _emit_message(self, message): self._overlay_object.add_message(message) @@ -688,34 +702,139 @@ class CreateDialog(QtWidgets.QDialog): self._update_help_btn() def _on_help_btn(self): + if self._desc_width_anim_timer.isActive(): + return + final_size = self.size() cur_sizes = self._splitter_widget.sizes() - spacing = self._splitter_widget.handleWidth() + + if self._desc_widget_step == 0: + now_visible = self._detail_description_widget.isVisible() + else: + now_visible = self._desc_widget_step > 0 sizes = [] for idx, value in enumerate(cur_sizes): if idx < 3: sizes.append(value) - now_visible = self._detail_description_widget.isVisible() + self._last_full_width = final_size.width() + self._other_widgets_widths = list(sizes) + if now_visible: - width = final_size.width() - ( - spacing + self._detail_description_widget.width() - ) + cur_desc_width = self._detail_description_widget.width() + if cur_desc_width < 1: + cur_desc_width = 2 + step_size = int(cur_desc_width / 5) + if step_size < 1: + step_size = 1 + + step_size *= -1 + expected_width = 0 + desc_width = cur_desc_width - 1 + width = final_size.width() - 1 + min_max = desc_width + self._last_description_width = cur_desc_width else: - last_size = self._detail_description_widget.sizeHint().width() - width = final_size.width() + spacing + last_size - sizes.append(last_size) + self._detail_description_widget.setVisible(True) + handle = self._splitter_widget.handle(3) + desc_width = handle.sizeHint().width() + if self._last_description_width: + expected_width = self._last_description_width + else: + hint = self._detail_description_widget.sizeHint() + expected_width = hint.width() + + width = final_size.width() + desc_width + step_size = int(expected_width / 5) + if step_size < 1: + step_size = 1 + min_max = 0 + + self._detail_description_widget.setMinimumWidth(min_max) + self._detail_description_widget.setMaximumWidth(min_max) + self._expected_description_width = expected_width + self._desc_widget_step = step_size + + self._desc_width_anim_timer.start() + + sizes.append(desc_width) final_size.setWidth(width) - self._detail_description_widget.setVisible(not now_visible) self._splitter_widget.setSizes(sizes) self.resize(final_size) self._help_btn.set_expanded(not now_visible) + def _on_desc_animation(self): + current_width = self._detail_description_widget.width() + + desc_width = None + last_step = False + growing = self._desc_widget_step > 0 + + # Growing + if growing: + if current_width < self._expected_description_width: + desc_width = current_width + self._desc_widget_step + if desc_width >= self._expected_description_width: + desc_width = self._expected_description_width + last_step = True + + # Decreasing + elif self._desc_widget_step < 0: + if current_width > self._expected_description_width: + desc_width = current_width + self._desc_widget_step + if desc_width <= self._expected_description_width: + desc_width = self._expected_description_width + last_step = True + + if desc_width is None: + self._desc_widget_step = 0 + self._desc_width_anim_timer.stop() + return + + if last_step and not growing: + self._detail_description_widget.setVisible(False) + QtWidgets.QApplication.processEvents() + + width = self._last_full_width + handle_width = self._splitter_widget.handle(3).width() + if growing: + width += (handle_width + desc_width) + else: + width -= self._last_description_width + if last_step: + width -= handle_width + else: + width += desc_width + + if not last_step or growing: + self._detail_description_widget.setMaximumWidth(desc_width) + self._detail_description_widget.setMinimumWidth(desc_width) + + window_size = self.size() + window_size.setWidth(width) + self.resize(window_size) + if not last_step: + return + + self._desc_widget_step = 0 + self._desc_width_anim_timer.stop() + + if not growing: + return + + self._detail_description_widget.setSizePolicy( + self._description_size_policy + ) + + sizes = list(self._other_widgets_widths) + sizes.append(desc_width) + self._splitter_widget.setSizes(sizes) + def _set_creator_detailed_text(self, creator): if not creator: self._detail_description_input.setPlainText("") From b53ed826b4aa120d133f8f795007389177cff511 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 2 May 2022 12:24:00 +0200 Subject: [PATCH 18/99] nuke: creator default knob to settings --- .../defaults/project_settings/nuke.json | 6 +- .../projects_schema/schema_project_nuke.json | 12 +- .../schemas/schema_nuke_knob_inputs.json | 151 ++++++++++++++++++ 3 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index ddf996b5f2..36daa92485 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -21,7 +21,8 @@ "defaults": [ "Main", "Mask" - ] + ], + "knobs": [] }, "CreateWritePrerender": { "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}", @@ -33,7 +34,8 @@ "Branch01", "Part01" ], - "reviewable": false + "reviewable": false, + "knobs": [] } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 9ab5fc65fb..dfd3306b2e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -87,7 +87,7 @@ "children": [ { "type": "dict", - "collapsible": false, + "collapsible": true, "key": "CreateWriteRender", "label": "CreateWriteRender", "is_group": true, @@ -104,12 +104,16 @@ "object_type": { "type": "text" } + }, + { + "type": "schema", + "name": "schema_nuke_knob_inputs" } ] }, { "type": "dict", - "collapsible": false, + "collapsible": true, "key": "CreateWritePrerender", "label": "CreateWritePrerender", "is_group": true, @@ -136,6 +140,10 @@ "type": "boolean", "key": "reviewable", "label": "Add reviewable toggle" + }, + { + "type": "schema", + "name": "schema_nuke_knob_inputs" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json new file mode 100644 index 0000000000..0d03b89288 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json @@ -0,0 +1,151 @@ +{ + "type": "collapsible-wrap", + "label": "Knob defaults", + "collapsible": true, + "collapsed": true, + "children": [{ + "type": "list", + "key": "knobs", + "object_type": { + "type": "dict-conditional", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "string", + "label": "String", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + }, + { + "key": "bool", + "label": "Boolean", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "key": "number", + "label": "Number", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "number", + "key": "value", + "default": 1, + "decimal": 0 + } + + ] + }, + { + "key": "decimal_number", + "label": "Decimal number", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "number", + "key": "value", + "default": 1, + "decimal": 4 + } + + ] + }, + { + "key": "2d_vector", + "label": "2D vector", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "list-strict", + "key": "value", + "label": "Value", + "object_types": [ + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4 + } + ] + } + ] + }, + { + "key": "3d_vector", + "label": "3D vector", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "list-strict", + "key": "value", + "label": "Value", + "object_types": [ + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4 + } + ] + } + ] + } + ] + } + }] +} From 8e4dc740e8dea2b87218754816b9501998284461 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 2 May 2022 12:24:20 +0200 Subject: [PATCH 19/99] nuke: adding default knobs to created node --- openpype/hosts/nuke/api/lib.py | 25 +++++++++++++++++++ openpype/hosts/nuke/api/plugin.py | 4 ++- .../plugins/create/create_write_render.py | 8 +++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3223feaec7..065fe9beb2 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -858,6 +858,7 @@ def create_write_node(name, data, input=None, prenodes=None, Return: node (obj): group node with avalon data as Knobs ''' + knob_overrides = data.get("knobs", []) imageio_writes = get_created_node_imageio_setting(**data) for knob in imageio_writes["knobs"]: @@ -1061,6 +1062,30 @@ def create_write_node(name, data, input=None, prenodes=None, tile_color = _data.get("tile_color", "0xff0000ff") GN["tile_color"].setValue(tile_color) + # overrie knob values from settings + for knob in knob_overrides: + knob_type = knob["type"] + knob_name = knob["name"] + knob_value = knob["value"] + if knob_name not in GN.knobs(): + continue + if not knob_value: + continue + + # set correctly knob types + if knob_type == "string": + knob_value = str(knob_value) + if knob_type == "number": + knob_value = int(knob_value) + if knob_type == "decimal_number": + knob_value = float(knob_value) + if knob_type == "bool": + knob_value = bool(knob_value) + if knob_type in ["2d_vector", "3d_vector"]: + knob_value = list(knob_value) + + GN[knob_name].setValue(knob_value) + return GN diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index fdb5930cb2..37c3633d2c 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -605,6 +605,7 @@ class AbstractWriteRender(OpenPypeCreator): family = "render" icon = "sign-out" defaults = ["Main", "Mask"] + knobs = [] def __init__(self, *args, **kwargs): super(AbstractWriteRender, self).__init__(*args, **kwargs) @@ -672,7 +673,8 @@ class AbstractWriteRender(OpenPypeCreator): "nodeclass": self.n_class, "families": [self.family], "avalon": self.data, - "subset": self.data["subset"] + "subset": self.data["subset"], + "knobs": self.knobs } # add creator data diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 18a101546f..36a7b5c33f 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -13,6 +13,7 @@ class CreateWriteRender(plugin.AbstractWriteRender): family = "render" icon = "sign-out" defaults = ["Main", "Mask"] + knobs = [] def __init__(self, *args, **kwargs): super(CreateWriteRender, self).__init__(*args, **kwargs) @@ -38,13 +39,12 @@ class CreateWriteRender(plugin.AbstractWriteRender): } ] - write_node = create_write_node( + return create_write_node( self.data["subset"], write_data, input=selected_node, - prenodes=_prenodes) - - return write_node + prenodes=_prenodes + ) def _modify_write_node(self, write_node): return write_node From d0b3cf73c4c5e6967a3f2431626b6c02771eab66 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 2 May 2022 17:11:12 +0200 Subject: [PATCH 20/99] nuke: extracting method for knob types --- openpype/hosts/nuke/api/lib.py | 36 ++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 065fe9beb2..a002e02ea3 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1062,31 +1062,43 @@ def create_write_node(name, data, input=None, prenodes=None, tile_color = _data.get("tile_color", "0xff0000ff") GN["tile_color"].setValue(tile_color) - # overrie knob values from settings - for knob in knob_overrides: + # finally add knob overrides + set_node_knobs_from_settings(GN, knob_overrides) + + return GN + + +def set_node_knobs_from_settings(node, knob_settings): + """ Overriding knob values from settings + + Using `schema_nuke_knob_inputs` for knob type definitions. + + Args: + node (nuke.Node): nuke node + knob_settings (list): list of dict. Keys are `type`, `name`, `value` + """ + for knob in knob_settings: knob_type = knob["type"] knob_name = knob["name"] knob_value = knob["value"] - if knob_name not in GN.knobs(): + if knob_name not in node.knobs(): continue if not knob_value: continue # set correctly knob types - if knob_type == "string": - knob_value = str(knob_value) - if knob_type == "number": - knob_value = int(knob_value) - if knob_type == "decimal_number": - knob_value = float(knob_value) if knob_type == "bool": knob_value = bool(knob_value) + elif knob_type == "decimal_number": + knob_value = float(knob_value) + elif knob_type == "number": + knob_value = int(knob_value) + elif knob_type == "string": + knob_value = str(knob_value) if knob_type in ["2d_vector", "3d_vector"]: knob_value = list(knob_value) - GN[knob_name].setValue(knob_value) - - return GN + node[knob_name].setValue(knob_value) def add_rendering_knobs(node, farm=True): From 4b7f17c77eca959fe4337e148311f6dbd878c136 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 2 May 2022 18:01:31 +0200 Subject: [PATCH 21/99] nuke: adding hex and color types to nuke knob schema --- .../schemas/schema_nuke_knob_inputs.json | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json index 0d03b89288..03eea039d6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json @@ -27,6 +27,22 @@ } ] }, + { + "key": "hex", + "label": "Hexadecimal (0x)", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + }, { "key": "bool", "label": "Boolean", @@ -144,6 +160,48 @@ ] } ] + }, + { + "key": "color", + "label": "Color", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "list-strict", + "key": "value", + "label": "Value", + "object_types": [ + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4 + }, + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4 + } + ] + } + ] } ] } From a72aaecc0d1a640416e321ba5d4fe51a03b7fe93 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 2 May 2022 18:02:03 +0200 Subject: [PATCH 22/99] nuke: solving hex and color knob types --- openpype/hosts/nuke/api/lib.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a002e02ea3..82c92f06f9 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import re import six import platform @@ -1086,6 +1087,11 @@ def set_node_knobs_from_settings(node, knob_settings): if not knob_value: continue + # first convert string types to string + # just to ditch unicode + if isinstance(knob_value, six.text_type): + knob_value = str(knob_value) + # set correctly knob types if knob_type == "bool": knob_value = bool(knob_value) @@ -1094,9 +1100,16 @@ def set_node_knobs_from_settings(node, knob_settings): elif knob_type == "number": knob_value = int(knob_value) elif knob_type == "string": - knob_value = str(knob_value) - if knob_type in ["2d_vector", "3d_vector"]: - knob_value = list(knob_value) + knob_value = knob_value + elif knob_type == "hex": + if not knob_value.startswith("0x"): + raise ValueError( + "Check your settings! Input Hexa is wrong! \n{}".format( + pformat(knob_settings) + )) + knob_value = int(knob_value, 16) + if knob_type in ["2d_vector", "3d_vector", "color"]: + knob_value = [float(v) for v in knob_value] node[knob_name].setValue(knob_value) From e6326570f8770251658b531ff3b3bd64c37a99f2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 2 May 2022 18:35:58 +0200 Subject: [PATCH 23/99] help button is resized and has more content --- openpype/style/style.css | 7 +- .../tools/publisher/widgets/create_dialog.py | 115 +++++++++++++++--- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 4032732a78..8eeae1a58a 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -866,16 +866,21 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #CreateDialogHelpButton { background: rgba(255, 255, 255, 31); + border-top-left-radius: 0.2em; + border-bottom-left-radius: 0.2em; border-top-right-radius: 0; border-bottom-right-radius: 0; font-size: 10pt; font-weight: bold; - padding: 3px 3px 3px 3px; + padding: 0px; } #CreateDialogHelpButton:hover { background: rgba(255, 255, 255, 63); } +#CreateDialogHelpButton QWidget { + background: transparent; +} #PublishLogConsole { font-family: "Noto Sans Mono"; diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 10be47c517..8b2710b165 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -3,6 +3,7 @@ import re import traceback import copy +import qtawesome try: import commonmark except Exception: @@ -15,7 +16,8 @@ from openpype.pipeline.create import ( ) from openpype.tools.utils import ( ErrorMessageBox, - MessageOverlayObject + MessageOverlayObject, + ClickableFrame, ) from .widgets import IconValuePixmapLabel @@ -186,13 +188,43 @@ class CreatorShortDescWidget(QtWidgets.QWidget): self._description_label.setText(description) -class HelpButton(QtWidgets.QPushButton): - resized = QtCore.Signal() +class HelpButton(ClickableFrame): + resized = QtCore.Signal(int) + question_mark_icon_name = "fa.question" + help_icon_name = "fa.question-circle" + hide_icon_name = "fa.angle-left" def __init__(self, *args, **kwargs): super(HelpButton, self).__init__(*args, **kwargs) self.setObjectName("CreateDialogHelpButton") + question_mark_label = QtWidgets.QLabel(self) + help_widget = QtWidgets.QWidget(self) + + help_question = QtWidgets.QLabel(help_widget) + help_label = QtWidgets.QLabel("Help", help_widget) + hide_icon = QtWidgets.QLabel(help_widget) + + help_layout = QtWidgets.QHBoxLayout(help_widget) + help_layout.setContentsMargins(0, 0, 5, 0) + help_layout.addWidget(help_question, 0) + help_layout.addWidget(help_label, 0) + help_layout.addStretch(1) + help_layout.addWidget(hide_icon, 0) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(question_mark_label, 0) + layout.addWidget(help_widget, 1) + + help_widget.setVisible(False) + + self._question_mark_label = question_mark_label + self._help_widget = help_widget + self._help_question = help_question + self._hide_icon = hide_icon + self._expanded = None self.set_expanded() @@ -202,27 +234,52 @@ class HelpButton(QtWidgets.QPushButton): return expanded = False self._expanded = expanded - if expanded: - text = "<" + self._help_widget.setVisible(expanded) + self._update_content() + + def _update_content(self): + width = self.get_icon_width() + if self._expanded: + question_mark_pix = QtGui.QPixmap(width, width) + question_mark_pix.fill(QtCore.Qt.transparent) + else: - text = "?" - self.setText(text) + question_mark_icon = qtawesome.icon( + self.question_mark_icon_name, color=QtCore.Qt.white + ) + question_mark_pix = question_mark_icon.pixmap(width, width) - self._update_size() + hide_icon = qtawesome.icon( + self.hide_icon_name, color=QtCore.Qt.white + ) + help_question_icon = qtawesome.icon( + self.help_icon_name, color=QtCore.Qt.white + ) + self._question_mark_label.setPixmap(question_mark_pix) + self._question_mark_label.setMaximumWidth(width) + self._hide_icon.setPixmap(hide_icon.pixmap(width, width)) + self._help_question.setPixmap(help_question_icon.pixmap(width, width)) - def _update_size(self): - new_size = self.minimumSizeHint() - if self.size() != new_size: - self.resize(new_size) - self.resized.emit() + def get_icon_width(self): + metrics = self.fontMetrics() + return metrics.height() + + def set_pos_and_size(self, pos_x, pos_y, width, height): + update_icon = self.height() != height + self.move(pos_x, pos_y) + self.resize(width, height) + + if update_icon: + self._update_content() + self.updateGeometry() def showEvent(self, event): super(HelpButton, self).showEvent(event) - self._update_size() + self.resized.emit(self.height()) def resizeEvent(self, event): super(HelpButton, self).resizeEvent(event) - self._update_size() + self.resized.emit(self.height()) class CreateDialog(QtWidgets.QDialog): @@ -692,14 +749,32 @@ class CreateDialog(QtWidgets.QDialog): self._set_creator_by_identifier(identifier) def _update_help_btn(self): - pos_x = self.width() - self._help_btn.width() - point = self._creator_short_desc_widget.rect().topRight() + short_desc_rect = self._creator_short_desc_widget.rect() + height = short_desc_rect.height() + + point = short_desc_rect.topRight() mapped_point = self._creator_short_desc_widget.mapTo(self, point) pos_y = mapped_point.y() - self._help_btn.move(max(0, pos_x), max(0, pos_y)) - def _on_help_btn_resize(self): - self._update_help_btn() + icon_width = self._help_btn.get_icon_width() + + pos_x = self.width() - icon_width + if self._detail_placoholder_widget.isVisible(): + pos_x -= ( + self._detail_placoholder_widget.width() + + self._splitter_widget.handle(3).width() + ) + + width = self.width() - pos_x + + self._help_btn.set_pos_and_size( + max(0, pos_x), max(0, pos_y), + width, height + ) + + def _on_help_btn_resize(self, height): + if self._creator_short_desc_widget.height() != height: + self._update_help_btn() def _on_help_btn(self): if self._desc_width_anim_timer.isActive(): From b7f859c0b00c41cb511f256160b4d17d7164e616 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 2 May 2022 19:27:27 +0200 Subject: [PATCH 24/99] fixed description min/max sizes --- .../tools/publisher/widgets/create_dialog.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 8b2710b165..bcf87dea6a 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -492,6 +492,7 @@ class CreateDialog(QtWidgets.QDialog): creator_short_desc_widget.height_changed.connect( self._on_description_height_change ) + splitter_widget.splitterMoved.connect(self._on_splitter_move) controller.add_plugins_refresh_callback(self._on_plugins_refresh) @@ -534,6 +535,7 @@ class CreateDialog(QtWidgets.QDialog): self._last_description_width = None self._last_full_width = 0 self._expected_description_width = 0 + self._last_desc_max_width = None self._other_widgets_widths = [] def _emit_message(self, message): @@ -750,14 +752,18 @@ class CreateDialog(QtWidgets.QDialog): def _update_help_btn(self): short_desc_rect = self._creator_short_desc_widget.rect() - height = short_desc_rect.height() - point = short_desc_rect.topRight() + # point = short_desc_rect.topRight() + point = short_desc_rect.center() mapped_point = self._creator_short_desc_widget.mapTo(self, point) - pos_y = mapped_point.y() - + # pos_y = mapped_point.y() + center_pos_y = mapped_point.y() icon_width = self._help_btn.get_icon_width() + _height = int(icon_width * 2.5) + height = min(_height, short_desc_rect.height()) + pos_y = center_pos_y - int(height / 2) + pos_x = self.width() - icon_width if self._detail_placoholder_widget.isVisible(): pos_x -= ( @@ -776,6 +782,9 @@ class CreateDialog(QtWidgets.QDialog): if self._creator_short_desc_widget.height() != height: self._update_help_btn() + def _on_splitter_move(self, *args): + self._update_help_btn() + def _on_help_btn(self): if self._desc_width_anim_timer.isActive(): return @@ -827,6 +836,10 @@ class CreateDialog(QtWidgets.QDialog): step_size = 1 min_max = 0 + if self._last_desc_max_width is None: + self._last_desc_max_width = ( + self._detail_description_widget.maximumWidth() + ) self._detail_description_widget.setMinimumWidth(min_max) self._detail_description_widget.setMaximumWidth(min_max) self._expected_description_width = expected_width @@ -902,6 +915,10 @@ class CreateDialog(QtWidgets.QDialog): if not growing: return + self._detail_description_widget.setMinimumWidth(0) + self._detail_description_widget.setMaximumWidth( + self._last_desc_max_width + ) self._detail_description_widget.setSizePolicy( self._description_size_policy ) From c608eeb2623bf97ccf65d876404d69c7e8b9988d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 May 2022 21:35:09 +0200 Subject: [PATCH 25/99] Remove remaining imports from avalon --- openpype/hosts/blender/api/plugin.py | 2 +- openpype/hosts/fusion/api/pipeline.py | 3 ++- openpype/hosts/harmony/api/lib.py | 2 +- openpype/hosts/hiero/api/lib.py | 11 +++++------ openpype/hosts/houdini/plugins/load/show_usdview.py | 11 +++++------ openpype/hosts/maya/plugins/load/_load_animation.py | 2 +- openpype/hosts/nuke/api/lib.py | 2 +- openpype/widgets/project_settings.py | 2 +- tests/lib/testing_classes.py | 2 +- 9 files changed, 18 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 3207f543b7..c59be8d7ff 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -266,7 +266,7 @@ class AssetLoader(LoaderPlugin): # Only containerise if it's not already a collection from a .blend file. # representation = context["representation"]["name"] # if representation != "blend": - # from avalon.blender.pipeline import containerise + # from openpype.hosts.blender.api.pipeline import containerise # return containerise( # name=name, # namespace=namespace, diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 0867b464d5..54002f9f51 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -45,7 +45,8 @@ def install(): This is where you install menus and register families, data and loaders into fusion. - It is called automatically when installing via `api.install(avalon.fusion)` + It is called automatically when installing via + `openpype.pipeline.install_host(openpype.hosts.fusion.api)` See the Maya equivalent for inspiration on how to implement this. diff --git a/openpype/hosts/harmony/api/lib.py b/openpype/hosts/harmony/api/lib.py index 53fd0f07dd..e5e7ad1b7e 100644 --- a/openpype/hosts/harmony/api/lib.py +++ b/openpype/hosts/harmony/api/lib.py @@ -463,7 +463,7 @@ def imprint(node_id, data, remove=False): remove (bool): Removes the data from the scene. Example: - >>> from avalon.harmony import lib + >>> from openpype.hosts.harmony.api import lib >>> node = "Top/Display" >>> data = {"str": "someting", "int": 1, "float": 0.32, "bool": True} >>> lib.imprint(layer, data) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 0e64ddcaf5..2a4cd03b76 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -553,10 +553,10 @@ class PublishAction(QtWidgets.QAction): # # ''' # import hiero.core -# from avalon.nuke import imprint -# from pype.hosts.nuke import ( -# lib as nklib -# ) +# from openpype.hosts.nuke.api.lib import ( +# BuildWorkfile, +# imprint +# ) # # # check if the file exists if does then Raise "File exists!" # if os.path.exists(filepath): @@ -583,8 +583,7 @@ class PublishAction(QtWidgets.QAction): # # nuke_script.addNode(root_node) # -# # here to call pype.hosts.nuke.lib.BuildWorkfile -# script_builder = nklib.BuildWorkfile( +# script_builder = BuildWorkfile( # root_node=root_node, # root_path=root_path, # nodes=nuke_script.getNodes(), diff --git a/openpype/hosts/houdini/plugins/load/show_usdview.py b/openpype/hosts/houdini/plugins/load/show_usdview.py index 8066615181..2737bc40fa 100644 --- a/openpype/hosts/houdini/plugins/load/show_usdview.py +++ b/openpype/hosts/houdini/plugins/load/show_usdview.py @@ -1,3 +1,7 @@ +import os +import subprocess + +from openpype.lib.vendor_bin_utils import find_executable from openpype.pipeline import load @@ -14,12 +18,7 @@ class ShowInUsdview(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): - import os - import subprocess - - import avalon.lib as lib - - usdview = lib.which("usdview") + usdview = find_executable("usdview") filepath = os.path.normpath(self.fname) filepath = filepath.replace("\\", "/") diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index bce1f0fc67..9c37e498ef 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -2,7 +2,7 @@ import openpype.hosts.maya.api.plugin class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """Specific loader of Alembic for the avalon.animation family""" + """Loader to reference an Alembic file""" families = ["animation", "camera", diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3223feaec7..f0af20289c 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -193,7 +193,7 @@ def imprint(node, data, tab=None): Examples: ``` import nuke - from avalon.nuke import lib + from openpype.hosts.nuke.api import lib node = nuke.createNode("NoOp") data = { diff --git a/openpype/widgets/project_settings.py b/openpype/widgets/project_settings.py index 43ff9f2789..687e17b3bf 100644 --- a/openpype/widgets/project_settings.py +++ b/openpype/widgets/project_settings.py @@ -4,7 +4,7 @@ import platform from Qt import QtCore, QtGui, QtWidgets -from avalon import style +from openpype import style import ftrack_api diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 7dfbf6fd0d..f991f02227 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -153,7 +153,7 @@ class ModuleUnitTest(BaseTest): Database prepared from dumps with 'db_setup' fixture. """ - from avalon.api import AvalonMongoDB + from openpype.pipeline import AvalonMongoDB dbcon = AvalonMongoDB() dbcon.Session["AVALON_PROJECT"] = self.TEST_PROJECT_NAME yield dbcon From 9e2f7f328b5746aa9c2b617520c85e672233dcef Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 May 2022 21:43:01 +0200 Subject: [PATCH 26/99] Cleanup some Loader docstrings --- openpype/hosts/fusion/plugins/load/actions.py | 4 ++-- openpype/hosts/houdini/plugins/load/actions.py | 4 ++-- openpype/hosts/houdini/plugins/load/load_alembic.py | 2 +- openpype/hosts/houdini/plugins/load/load_camera.py | 2 +- openpype/hosts/houdini/plugins/load/load_image.py | 4 ++-- openpype/hosts/houdini/plugins/load/load_vdb.py | 2 +- openpype/hosts/maya/plugins/load/_load_animation.py | 2 +- openpype/hosts/maya/plugins/load/actions.py | 4 ++-- openpype/hosts/maya/plugins/load/load_ass.py | 2 +- openpype/hosts/maya/plugins/load/load_gpucache.py | 2 +- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- openpype/hosts/maya/plugins/load/load_vdb_to_vray.py | 1 + openpype/hosts/nuke/plugins/load/actions.py | 4 ++-- 13 files changed, 18 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py index bc59cec77f..819c9272fd 100644 --- a/openpype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -6,7 +6,7 @@ from openpype.pipeline import load class FusionSetFrameRangeLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Set frame range excluding pre- and post-handles""" families = ["animation", "camera", @@ -40,7 +40,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin): class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Set frame range including pre- and post-handles""" families = ["animation", "camera", diff --git a/openpype/hosts/houdini/plugins/load/actions.py b/openpype/hosts/houdini/plugins/load/actions.py index 63d74c39a5..637be1513d 100644 --- a/openpype/hosts/houdini/plugins/load/actions.py +++ b/openpype/hosts/houdini/plugins/load/actions.py @@ -6,7 +6,7 @@ from openpype.pipeline import load class SetFrameRangeLoader(load.LoaderPlugin): - """Set Houdini frame range""" + """Set frame range excluding pre- and post-handles""" families = [ "animation", @@ -44,7 +44,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): - """Set Maya frame range including pre- and post-handles""" + """Set frame range including pre- and post-handles""" families = [ "animation", diff --git a/openpype/hosts/houdini/plugins/load/load_alembic.py b/openpype/hosts/houdini/plugins/load/load_alembic.py index 0214229d5a..96e666b255 100644 --- a/openpype/hosts/houdini/plugins/load/load_alembic.py +++ b/openpype/hosts/houdini/plugins/load/load_alembic.py @@ -7,7 +7,7 @@ from openpype.hosts.houdini.api import pipeline class AbcLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Load Alembic""" families = ["model", "animation", "pointcache", "gpuCache"] label = "Load Alembic" diff --git a/openpype/hosts/houdini/plugins/load/load_camera.py b/openpype/hosts/houdini/plugins/load/load_camera.py index ef57d115da..059ad11a76 100644 --- a/openpype/hosts/houdini/plugins/load/load_camera.py +++ b/openpype/hosts/houdini/plugins/load/load_camera.py @@ -78,7 +78,7 @@ def transfer_non_default_values(src, dest, ignore=None): class CameraLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Load camera from an Alembic file""" families = ["camera"] label = "Load Camera (abc)" diff --git a/openpype/hosts/houdini/plugins/load/load_image.py b/openpype/hosts/houdini/plugins/load/load_image.py index 671f08f18f..928c2ee734 100644 --- a/openpype/hosts/houdini/plugins/load/load_image.py +++ b/openpype/hosts/houdini/plugins/load/load_image.py @@ -42,9 +42,9 @@ def get_image_avalon_container(): class ImageLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Load images into COP2""" - families = ["colorbleed.imagesequence"] + families = ["imagesequence"] label = "Load Image (COP2)" representations = ["*"] order = -10 diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 06bb9e45e4..bff0f8b0bf 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -9,7 +9,7 @@ from openpype.hosts.houdini.api import pipeline class VdbLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Load VDB""" families = ["vdbcache"] label = "Load VDB" diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index bce1f0fc67..9c37e498ef 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -2,7 +2,7 @@ import openpype.hosts.maya.api.plugin class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """Specific loader of Alembic for the avalon.animation family""" + """Loader to reference an Alembic file""" families = ["animation", "camera", diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 483ad32402..4b7871a40c 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -10,7 +10,7 @@ from openpype.hosts.maya.api.lib import ( class SetFrameRangeLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Set frame range excluding pre- and post-handles""" families = ["animation", "camera", @@ -44,7 +44,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Set frame range including pre- and post-handles""" families = ["animation", "camera", diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 18de4df3b1..a284b7ec1f 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -16,7 +16,7 @@ from openpype.hosts.maya.api.pipeline import containerise class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """Load the Proxy""" + """Load Arnold Proxy as reference""" families = ["ass"] representations = ["ass"] diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index 591e568e4c..6d5e945508 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -8,7 +8,7 @@ from openpype.api import get_project_settings class GpuCacheLoader(load.LoaderPlugin): - """Load model Alembic as gpuCache""" + """Load Alembic as gpuCache""" families = ["model"] representations = ["abc"] diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index a8875cf216..d65b5a2c1e 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -12,7 +12,7 @@ from openpype.hosts.maya.api.lib import maintained_selection class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """Load the model""" + """Reference file""" families = ["model", "pointcache", diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py index 4f14235bfb..3a16264ec0 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -74,6 +74,7 @@ def _fix_duplicate_vvg_callbacks(): class LoadVDBtoVRay(load.LoaderPlugin): + """Load OpenVDB in a V-Ray Volume Grid""" families = ["vdbcache"] representations = ["vdb"] diff --git a/openpype/hosts/nuke/plugins/load/actions.py b/openpype/hosts/nuke/plugins/load/actions.py index 81840b3a38..d364a4f3a1 100644 --- a/openpype/hosts/nuke/plugins/load/actions.py +++ b/openpype/hosts/nuke/plugins/load/actions.py @@ -9,7 +9,7 @@ log = Logger().get_logger(__name__) class SetFrameRangeLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Set frame range excluding pre- and post-handles""" families = ["animation", "camera", @@ -43,7 +43,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): - """Specific loader of Alembic for the avalon.animation family""" + """Set frame range including pre- and post-handles""" families = ["animation", "camera", From 80494c91c145a01c7aa2cf5017af8c89ecb6a4c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 May 2022 22:29:38 +0200 Subject: [PATCH 27/99] Fix typo --- openpype/modules/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 23c908299f..58ad3a8d2f 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -310,7 +310,7 @@ def _load_modules(): init_path = os.path.join(fullpath, "__init__.py") if not os.path.exists(init_path): log.debug(( - "Module directory does not contan __init__.py file {}" + "Module directory does not contain __init__.py file {}" ).format(fullpath)) continue @@ -357,7 +357,7 @@ def _load_modules(): init_path = os.path.join(fullpath, "__init__.py") if not os.path.exists(init_path): log.debug(( - "Module directory does not contan __init__.py file {}" + "Module directory does not contain __init__.py file {}" ).format(fullpath)) continue From 4fc8617bd1037b1b77b34cb903c85ddbb651a1d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 May 2022 22:32:23 +0200 Subject: [PATCH 28/99] Fix typo in comment --- openpype/modules/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 58ad3a8d2f..e280589548 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -353,7 +353,7 @@ def _load_modules(): basename, ext = os.path.splitext(filename) if os.path.isdir(fullpath): - # Check existence of init fil + # Check existence of init file init_path = os.path.join(fullpath, "__init__.py") if not os.path.exists(init_path): log.debug(( From 445fc679f1752def211f75a298ceb3a8af80f889 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 May 2022 22:32:47 +0200 Subject: [PATCH 29/99] And fix the other typo in comment --- openpype/modules/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index e280589548..5ad1fc71c4 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -306,7 +306,7 @@ def _load_modules(): basename, ext = os.path.splitext(filename) if os.path.isdir(fullpath): - # Check existence of init fil + # Check existence of init file init_path = os.path.join(fullpath, "__init__.py") if not os.path.exists(init_path): log.debug(( From ef7798d5028573a4384ebf38db33eae7cf47b98e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 2 May 2022 23:04:33 +0200 Subject: [PATCH 30/99] Fix coloring of TrayModuleManager output --- openpype/lib/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/terminal.py b/openpype/lib/terminal.py index 5121b6ec26..f6072ed209 100644 --- a/openpype/lib/terminal.py +++ b/openpype/lib/terminal.py @@ -98,7 +98,7 @@ class Terminal: r"\*\*\* WRN": _SB + _LY + r"*** WRN" + _RST, r" \- ": _SB + _LY + r" - " + _RST, r"\[ ": _SB + _LG + r"[ " + _RST, - r"\]": _SB + _LG + r"]" + _RST, + r" \]": _SB + _LG + r" ]" + _RST, r"{": _LG + r"{", r"}": r"}" + _RST, r"\(": _LY + r"(", From 4f3cbeb9a94a237da4f12858bb4ae95f7cc581d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 11:29:57 +0200 Subject: [PATCH 31/99] fix compositing order --- openpype/hosts/tvpaint/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/lib.py b/openpype/hosts/tvpaint/lib.py index 715ebb4a9d..c67ab1e4fb 100644 --- a/openpype/hosts/tvpaint/lib.py +++ b/openpype/hosts/tvpaint/lib.py @@ -573,7 +573,7 @@ def composite_rendered_layers( layer_ids_by_position[layer_position] = layer["layer_id"] # Sort layer positions - sorted_positions = tuple(sorted(layer_ids_by_position.keys())) + sorted_positions = tuple(reversed(sorted(layer_ids_by_position.keys()))) # Prepare variable where filepaths without any rendered content # - transparent will be created transparent_filepaths = set() From 518ab19a0b58dbe78c7051aa1f69fc4e28f1e310 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 12:11:23 +0200 Subject: [PATCH 32/99] fix missing openpype_versions variable for headless mode --- start.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/start.py b/start.py index 4d4801c1e5..750552399f 100644 --- a/start.py +++ b/start.py @@ -1029,6 +1029,9 @@ def boot(): message = str(exc) _print(message) if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1": + openpype_versions = bootstrap.find_openpype( + include_zips=True, staging=use_staging + ) list_versions(openpype_versions, local_version) else: igniter.show_message_dialog("Version not found", message) @@ -1053,6 +1056,9 @@ def boot(): message = str(exc) _print(message) if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1": + openpype_versions = bootstrap.find_openpype( + include_zips=True, staging=use_staging + ) list_versions(openpype_versions, local_version) else: igniter.show_message_dialog("Version not found", message) From 8e7b27cff14cba8330b7f8c9148b64130c21f2f0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 12:16:19 +0200 Subject: [PATCH 33/99] nuke: change `string` type to `text` --- openpype/hosts/nuke/api/lib.py | 2 +- openpype/settings/defaults/project_settings/nuke.json | 6 +++--- .../projects_schema/schemas/schema_nuke_knob_inputs.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 82c92f06f9..e6e61844ba 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1099,7 +1099,7 @@ def set_node_knobs_from_settings(node, knob_settings): knob_value = float(knob_value) elif knob_type == "number": knob_value = int(knob_value) - elif knob_type == "string": + elif knob_type == "text": knob_value = knob_value elif knob_type == "hex": if not knob_value.startswith("0x"): diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index c952d72bb1..4fc6caa57e 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -131,17 +131,17 @@ "reformat_node_add": false, "reformat_node_config": [ { - "type": "string", + "type": "text", "name": "type", "value": "to format" }, { - "type": "string", + "type": "text", "name": "format", "value": "HD_1080" }, { - "type": "string", + "type": "text", "name": "filter", "value": "Lanczos6" }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json index 03eea039d6..95913f5335 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json @@ -12,8 +12,8 @@ "enum_label": "Type", "enum_children": [ { - "key": "string", - "label": "String", + "key": "text", + "label": "Text", "children": [ { "type": "text", From 712d4c72abf7f40d26e7a781f06c69b0eb6ca214 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 12:23:36 +0200 Subject: [PATCH 34/99] moved print and validate versions logic to separated functions --- start.py | 86 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/start.py b/start.py index 750552399f..064b828744 100644 --- a/start.py +++ b/start.py @@ -897,6 +897,51 @@ def _bootstrap_from_code(use_version, use_staging): return version_path +def _boot_validate_versions(use_version, local_version): + _print(f">>> Validating version [ {use_version} ]") + openpype_versions = bootstrap.find_openpype(include_zips=True, + staging=True) + openpype_versions += bootstrap.find_openpype(include_zips=True, + staging=False) + v: OpenPypeVersion + found = [v for v in openpype_versions if str(v) == use_version] + if not found: + _print(f"!!! Version [ {use_version} ] not found.") + list_versions(openpype_versions, local_version) + sys.exit(1) + + # print result + result = bootstrap.validate_openpype_version( + bootstrap.get_version_path_from_list( + use_version, openpype_versions)) + + _print("{}{}".format( + ">>> " if result[0] else "!!! ", + bootstrap.validate_openpype_version( + bootstrap.get_version_path_from_list( + use_version, openpype_versions) + )[1]) + ) + + +def _boot_print_versions(use_staging, local_version, openpype_root): + if not use_staging: + _print("--- This will list only non-staging versions detected.") + _print(" To see staging versions, use --use-staging argument.") + else: + _print("--- This will list only staging versions detected.") + _print(" To see other version, omit --use-staging argument.") + _openpype_root = OPENPYPE_ROOT + openpype_versions = bootstrap.find_openpype(include_zips=True, + staging=use_staging) + if getattr(sys, 'frozen', False): + local_version = bootstrap.get_version(Path(_openpype_root)) + else: + local_version = OpenPypeVersion.get_installed_version_str() + + list_versions(openpype_versions, local_version) + + def boot(): """Bootstrap OpenPype.""" @@ -966,30 +1011,7 @@ def boot(): local_version = OpenPypeVersion.get_installed_version_str() if "validate" in commands: - _print(f">>> Validating version [ {use_version} ]") - openpype_versions = bootstrap.find_openpype(include_zips=True, - staging=True) - openpype_versions += bootstrap.find_openpype(include_zips=True, - staging=False) - v: OpenPypeVersion - found = [v for v in openpype_versions if str(v) == use_version] - if not found: - _print(f"!!! Version [ {use_version} ] not found.") - list_versions(openpype_versions, local_version) - sys.exit(1) - - # print result - result = bootstrap.validate_openpype_version( - bootstrap.get_version_path_from_list( - use_version, openpype_versions)) - - _print("{}{}".format( - ">>> " if result[0] else "!!! ", - bootstrap.validate_openpype_version( - bootstrap.get_version_path_from_list( - use_version, openpype_versions) - )[1]) - ) + _boot_validate_versions(use_version, local_version) sys.exit(1) if not openpype_path: @@ -999,21 +1021,7 @@ def boot(): os.environ["OPENPYPE_PATH"] = openpype_path if "print_versions" in commands: - if not use_staging: - _print("--- This will list only non-staging versions detected.") - _print(" To see staging versions, use --use-staging argument.") - else: - _print("--- This will list only staging versions detected.") - _print(" To see other version, omit --use-staging argument.") - _openpype_root = OPENPYPE_ROOT - openpype_versions = bootstrap.find_openpype(include_zips=True, - staging=use_staging) - if getattr(sys, 'frozen', False): - local_version = bootstrap.get_version(Path(_openpype_root)) - else: - local_version = OpenPypeVersion.get_installed_version_str() - - list_versions(openpype_versions, local_version) + _boot_print_versions(use_staging, local_version, OPENPYPE_ROOT) sys.exit(1) # ------------------------------------------------------------------------ From 80c7d177a6c3144fb7f8cb4464503ab53ec295cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 12:33:26 +0200 Subject: [PATCH 35/99] simplified modules file validations and imports --- openpype/modules/base.py | 103 +++++++++++++++------------------------ 1 file changed, 38 insertions(+), 65 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 5ad1fc71c4..b48de59fa0 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -290,49 +290,16 @@ def _load_modules(): log = PypeLogger.get_logger("ModulesLoader") - current_dir = os.path.abspath(os.path.dirname(__file__)) - processed_paths = set() - processed_paths.add(current_dir) - # Import default modules imported from 'openpype.modules' - for filename in os.listdir(current_dir): - # Ignore filenames - if ( - filename in IGNORED_FILENAMES - or filename in IGNORED_DEFAULT_FILENAMES - ): - continue - - fullpath = os.path.join(current_dir, filename) - basename, ext = os.path.splitext(filename) - - if os.path.isdir(fullpath): - # Check existence of init file - init_path = os.path.join(fullpath, "__init__.py") - if not os.path.exists(init_path): - log.debug(( - "Module directory does not contain __init__.py file {}" - ).format(fullpath)) - continue - - elif ext not in (".py", ): - continue - - try: - import_str = "openpype.modules.{}".format(basename) - new_import_str = "{}.{}".format(modules_key, basename) - default_module = __import__(import_str, fromlist=("", )) - sys.modules[new_import_str] = default_module - setattr(openpype_modules, basename, default_module) - - except Exception: - msg = ( - "Failed to import default module '{}'." - ).format(basename) - log.error(msg, exc_info=True) - # Look for OpenPype modules in paths defined with `get_module_dirs` # - dynamically imported OpenPype modules and addons - for dirpath in get_module_dirs(): + module_dirs = get_module_dirs() + # Add current directory at first place + # - has small differences in import logic + current_dir = os.path.abspath(os.path.dirname(__file__)) + module_dirs.insert(0, current_dir) + + processed_paths = set() + for dirpath in module_dirs: # Skip already processed paths if dirpath in processed_paths: continue @@ -344,39 +311,42 @@ def _load_modules(): ).format(dirpath)) continue + is_in_current_dir = dirpath == current_dir for filename in os.listdir(dirpath): # Ignore filenames if filename in IGNORED_FILENAMES: continue + if ( + is_in_current_dir + and filename in IGNORED_DEFAULT_FILENAMES + ): + continue + fullpath = os.path.join(dirpath, filename) basename, ext = os.path.splitext(filename) - if os.path.isdir(fullpath): - # Check existence of init file - init_path = os.path.join(fullpath, "__init__.py") - if not os.path.exists(init_path): - log.debug(( - "Module directory does not contain __init__.py file {}" - ).format(fullpath)) - continue - - elif ext not in (".py", ): - continue - # TODO add more logic how to define if folder is module or not # - check manifest and content of manifest try: - if os.path.isdir(fullpath): - # Module without init file can't be used as OpenPype module - # because the module class could not be imported - init_file = os.path.join(fullpath, "__init__.py") - if not os.path.exists(init_file): - log.info(( - "Skipping module directory because of" - " missing \"__init__.py\" file. \"{}\"" + if is_in_current_dir: + # Don't import dynamically + import_str = "openpype.modules.{}".format(basename) + new_import_str = "{}.{}".format(modules_key, basename) + default_module = __import__(import_str, fromlist=("", )) + sys.modules[new_import_str] = default_module + setattr(openpype_modules, basename, default_module) + + elif os.path.isdir(fullpath): + # Check existence of init file + init_path = os.path.join(fullpath, "__init__.py") + if not os.path.exists(init_path): + log.debug(( + "Module directory does not contan __init__.py" + " file {}" ).format(fullpath)) continue + import_module_from_dirpath(dirpath, filename, modules_key) elif ext in (".py", ): @@ -384,10 +354,13 @@ def _load_modules(): setattr(openpype_modules, basename, module) except Exception: - log.error( - "Failed to import '{}'.".format(fullpath), - exc_info=True - ) + if is_in_current_dir: + msg = "Failed to import default module '{}'.".format( + basename + ) + else: + msg = "Failed to import '{}'.".format(fullpath) + log.error(msg, exc_info=True) class _OpenPypeInterfaceMeta(ABCMeta): From 16af1a2347af8ae4f87f2095a1f63379312724dd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 12:41:17 +0200 Subject: [PATCH 36/99] moved validation much earlier --- openpype/modules/base.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index b48de59fa0..a85bedac31 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -326,11 +326,25 @@ def _load_modules(): fullpath = os.path.join(dirpath, filename) basename, ext = os.path.splitext(filename) + # Validations + if os.path.isdir(fullpath): + # Check existence of init file + init_path = os.path.join(fullpath, "__init__.py") + if not os.path.exists(init_path): + log.debug(( + "Module directory does not contan __init__.py" + " file {}" + ).format(fullpath)) + continue + + elif ext not in (".py", ): + continue + # TODO add more logic how to define if folder is module or not # - check manifest and content of manifest try: + # Don't import dynamically current directory modules if is_in_current_dir: - # Don't import dynamically import_str = "openpype.modules.{}".format(basename) new_import_str = "{}.{}".format(modules_key, basename) default_module = __import__(import_str, fromlist=("", )) @@ -338,18 +352,9 @@ def _load_modules(): setattr(openpype_modules, basename, default_module) elif os.path.isdir(fullpath): - # Check existence of init file - init_path = os.path.join(fullpath, "__init__.py") - if not os.path.exists(init_path): - log.debug(( - "Module directory does not contan __init__.py" - " file {}" - ).format(fullpath)) - continue - import_module_from_dirpath(dirpath, filename, modules_key) - elif ext in (".py", ): + else: module = import_filepath(fullpath) setattr(openpype_modules, basename, module) From 313382f2fb7d6957f3fe5a11688ac5c0d8e18783 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 12:46:40 +0200 Subject: [PATCH 37/99] fix OPENPYPE_ROOT usage --- start.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start.py b/start.py index 064b828744..cd1d95dd8f 100644 --- a/start.py +++ b/start.py @@ -931,11 +931,11 @@ def _boot_print_versions(use_staging, local_version, openpype_root): else: _print("--- This will list only staging versions detected.") _print(" To see other version, omit --use-staging argument.") - _openpype_root = OPENPYPE_ROOT + openpype_versions = bootstrap.find_openpype(include_zips=True, staging=use_staging) if getattr(sys, 'frozen', False): - local_version = bootstrap.get_version(Path(_openpype_root)) + local_version = bootstrap.get_version(Path(openpype_root)) else: local_version = OpenPypeVersion.get_installed_version_str() From 7903437ba76f6ddf0a0679d25a7169a56c669bad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 12:50:52 +0200 Subject: [PATCH 38/99] nuke: converting schema knobs to template schema - refactoring imageio nuke to the template - refactoring nuke publish bake move to the template --- .../defaults/project_anatomy/imageio.json | 33 ++- .../projects_schema/schema_project_nuke.json | 20 +- .../schemas/schema_anatomy_imageio.json | 54 +---- .../schemas/schema_nuke_knob_inputs.json | 209 ----------------- .../schemas/schema_nuke_publish.json | 106 +-------- .../schemas/template_nuke_knob_inputs.json | 222 ++++++++++++++++++ 6 files changed, 281 insertions(+), 363 deletions(-) delete mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index fedae994bf..2023dae23c 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -55,36 +55,44 @@ "nukeNodeClass": "Write", "knobs": [ { + "type": "text", "name": "file_type", "value": "exr" }, { + "type": "text", "name": "datatype", "value": "16 bit half" }, { + "type": "text", "name": "compression", "value": "Zip (1 scanline)" }, { + "type": "bool", "name": "autocrop", - "value": "True" + "value": true }, { + "type": "hex", "name": "tile_color", "value": "0xff0000ff" }, { + "type": "text", "name": "channels", "value": "rgb" }, { + "type": "text", "name": "colorspace", "value": "linear" }, { + "type": "bool", "name": "create_directories", - "value": "True" + "value": true } ] }, @@ -95,36 +103,44 @@ "nukeNodeClass": "Write", "knobs": [ { + "type": "text", "name": "file_type", "value": "exr" }, { + "type": "text", "name": "datatype", "value": "16 bit half" }, { + "type": "text", "name": "compression", "value": "Zip (1 scanline)" }, { + "type": "bool", "name": "autocrop", - "value": "False" + "value": true }, { + "type": "hex", "name": "tile_color", "value": "0xadab1dff" }, { + "type": "text", "name": "channels", "value": "rgb" }, { + "type": "text", "name": "colorspace", "value": "linear" }, { + "type": "bool", "name": "create_directories", - "value": "True" + "value": true } ] }, @@ -135,32 +151,39 @@ "nukeNodeClass": "Write", "knobs": [ { + "type": "text", "name": "file_type", "value": "tiff" }, { + "type": "text", "name": "datatype", "value": "16 bit" }, { + "type": "text", "name": "compression", "value": "Deflate" }, { + "type": "hex", "name": "tile_color", "value": "0x23ff00ff" }, { + "type": "text", "name": "channels", "value": "rgb" }, { + "type": "text", "name": "colorspace", "value": "sRGB" }, { + "type": "bool", "name": "create_directories", - "value": "True" + "value": true } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index dfd3306b2e..29ad8e3c6c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -106,8 +106,14 @@ } }, { - "type": "schema", - "name": "schema_nuke_knob_inputs" + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] } ] }, @@ -142,8 +148,14 @@ "label": "Add reviewable toggle" }, { - "type": "schema", - "name": "schema_nuke_knob_inputs" + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index 819f7121c4..ef8c907dda 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -272,29 +272,12 @@ "label": "Nuke Node Class" }, { - "type": "collapsible-wrap", - "label": "Knobs", - "collapsible": true, - "collapsed": true, - "children": [ + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ { - "key": "knobs", - "type": "list", - "object_type": { - "type": "dict", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] - } + "label": "Knobs", + "key": "knobs" } ] } @@ -333,29 +316,12 @@ "object_type": "text" }, { - "type": "collapsible-wrap", - "label": "Knobs overrides", - "collapsible": true, - "collapsed": true, - "children": [ + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ { - "key": "knobs", - "type": "list", - "object_type": { - "type": "dict", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] - } + "label": "Knobs overrides", + "key": "knobs" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json deleted file mode 100644 index 95913f5335..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_knob_inputs.json +++ /dev/null @@ -1,209 +0,0 @@ -{ - "type": "collapsible-wrap", - "label": "Knob defaults", - "collapsible": true, - "collapsed": true, - "children": [{ - "type": "list", - "key": "knobs", - "object_type": { - "type": "dict-conditional", - "enum_key": "type", - "enum_label": "Type", - "enum_children": [ - { - "key": "text", - "label": "Text", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] - }, - { - "key": "hex", - "label": "Hexadecimal (0x)", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] - }, - { - "key": "bool", - "label": "Boolean", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] - }, - { - "key": "number", - "label": "Number", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "number", - "key": "value", - "default": 1, - "decimal": 0 - } - - ] - }, - { - "key": "decimal_number", - "label": "Decimal number", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "number", - "key": "value", - "default": 1, - "decimal": 4 - } - - ] - }, - { - "key": "2d_vector", - "label": "2D vector", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "list-strict", - "key": "value", - "label": "Value", - "object_types": [ - { - "type": "number", - "key": "x", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "y", - "default": 1, - "decimal": 4 - } - ] - } - ] - }, - { - "key": "3d_vector", - "label": "3D vector", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "list-strict", - "key": "value", - "label": "Value", - "object_types": [ - { - "type": "number", - "key": "x", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "y", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "y", - "default": 1, - "decimal": 4 - } - ] - } - ] - }, - { - "key": "color", - "label": "Color", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "list-strict", - "key": "value", - "label": "Value", - "object_types": [ - { - "type": "number", - "key": "x", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "x", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "y", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "y", - "default": 1, - "decimal": 4 - } - ] - } - ] - } - ] - } - }] -} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index d67fb309bd..94b52bba13 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -253,108 +253,12 @@ "default": false }, { - "type": "collapsible-wrap", - "label": "Reformat Node Knobs", - "collapsible": true, - "collapsed": true, - "children": [ + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ { - "type": "list", - "key": "reformat_node_config", - "object_type": { - "type": "dict-conditional", - "enum_key": "type", - "enum_label": "Type", - "enum_children": [ - { - "key": "string", - "label": "String", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] - }, - { - "key": "bool", - "label": "Boolean", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] - }, - { - "key": "number", - "label": "Number", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "list-strict", - "key": "value", - "label": "Value", - "object_types": [ - { - "type": "number", - "key": "number", - "default": 1, - "decimal": 4 - } - ] - } - - ] - }, - { - "key": "list_numbers", - "label": "2 Numbers", - "children": [ - { - "type": "text", - "key": "name", - "label": "Name" - }, - { - "type": "list-strict", - "key": "value", - "label": "Value", - "object_types": [ - { - "type": "number", - "key": "x", - "default": 1, - "decimal": 4 - }, - { - "type": "number", - "key": "y", - "default": 1, - "decimal": 4 - } - ] - } - ] - } - ] - } + "label": "Reformat Node Knobs", + "key": "reformat_node_config" } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json new file mode 100644 index 0000000000..957c684013 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json @@ -0,0 +1,222 @@ +[ + { + "type": "collapsible-wrap", + "label": "{label}", + "collapsible": true, + "collapsed": true, + "children": [{ + "type": "list", + "key": "{key}", + "object_type": { + "type": "dict-conditional", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "text", + "label": "Text", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + }, + { + "key": "hex", + "label": "Hexadecimal (0x)", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + }, + { + "key": "bool", + "label": "Boolean", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "key": "number", + "label": "Number", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "number", + "key": "value", + "default": 1, + "decimal": 0, + "maximum": 99999999 + } + + ] + }, + { + "key": "decimal_number", + "label": "Decimal number", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "number", + "key": "value", + "default": 1, + "decimal": 4, + "maximum": 99999999 + } + + ] + }, + { + "key": "2d_vector", + "label": "2D vector", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "list-strict", + "key": "value", + "label": "Value", + "object_types": [ + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4, + "maximum": 99999999 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4, + "maximum": 99999999 + } + ] + } + ] + }, + { + "key": "3d_vector", + "label": "3D vector", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "list-strict", + "key": "value", + "label": "Value", + "object_types": [ + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4, + "maximum": 99999999 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4, + "maximum": 99999999 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4, + "maximum": 99999999 + } + ] + } + ] + }, + { + "key": "color", + "label": "Color", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "list-strict", + "key": "value", + "label": "Value", + "object_types": [ + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4, + "maximum": 99999999 + }, + { + "type": "number", + "key": "x", + "default": 1, + "decimal": 4, + "maximum": 99999999 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4, + "maximum": 99999999 + }, + { + "type": "number", + "key": "y", + "default": 1, + "decimal": 4, + "maximum": 99999999 + } + ] + } + ] + } + ] + } + }] + } +] From 317c27eeaf06f393122aec10508baf9f1b413f71 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 3 May 2022 14:04:40 +0200 Subject: [PATCH 39/99] modified log message Co-authored-by: Roy Nieterau --- openpype/modules/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index a85bedac31..629a2fa689 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -364,7 +364,7 @@ def _load_modules(): basename ) else: - msg = "Failed to import '{}'.".format(fullpath) + msg = "Failed to import module '{}'.".format(fullpath) log.error(msg, exc_info=True) From 0b306e2e77bed1dd939619baa9917075d9b3902d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 3 May 2022 14:04:49 +0200 Subject: [PATCH 40/99] fix typo Co-authored-by: Roy Nieterau --- openpype/modules/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 629a2fa689..0dd512ee8b 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -332,7 +332,7 @@ def _load_modules(): init_path = os.path.join(fullpath, "__init__.py") if not os.path.exists(init_path): log.debug(( - "Module directory does not contan __init__.py" + "Module directory does not contain __init__.py" " file {}" ).format(fullpath)) continue From dc94809957803a7ab57c027ff2b82b504fe78f38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 14:35:45 +0200 Subject: [PATCH 41/99] added icon to asset input dialog --- openpype/style/style.css | 19 ++++++++++++++- openpype/tools/publisher/widgets/widgets.py | 26 +++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 8eeae1a58a..93b13916c1 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1027,7 +1027,24 @@ VariantInputsWidget QToolButton { border-left: 1px solid {color:border}; } -#TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"] { +#AssetNameInputButton { + background: {color:bg-inputs}; + border: 1px solid {color:border}; + border-bottom-left-radius: 0px; + border-top-left-radius: 0px; + padding: 0px; + qproperty-iconSize: 11px 11px; +} +#AssetNameInputButton:disabled { + background: {color:bg-inputs-disabled}; +} +#AssetNameInput { + border-bottom-right-radius: 0px; + border-top-right-radius: 0px; + border-right: none; +} + +#TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"], #AssetNameInputButton[state="invalid"] { border-color: {color:publisher:error}; } diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 5ced469b59..1569e1a4c1 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -14,7 +14,8 @@ from openpype.tools.utils import ( PlaceholderLineEdit, IconButton, PixmapLabel, - BaseClickableFrame + BaseClickableFrame, + set_style_property, ) from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS from .assets_widget import AssetsDialog @@ -350,15 +351,32 @@ class AssetsField(BaseClickableFrame): name_input = ClickableLineEdit(self) name_input.setObjectName("AssetNameInput") + icon_name = "fa.window-maximize" + icon = qtawesome.icon(icon_name, color="white") + icon_btn = QtWidgets.QPushButton(self) + icon_btn.setIcon(icon) + icon_btn.setObjectName("AssetNameInputButton") + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) layout.addWidget(name_input, 1) + layout.addWidget(icon_btn, 0) + for widget in ( + name_input, + icon_btn + ): + size_policy = widget.sizePolicy() + size_policy.setVerticalPolicy(size_policy.MinimumExpanding) + widget.setSizePolicy(size_policy) name_input.clicked.connect(self._mouse_release_callback) + icon_btn.clicked.connect(self._mouse_release_callback) dialog.finished.connect(self._on_dialog_finish) self._dialog = dialog self._name_input = name_input + self._icon_btn = icon_btn self._origin_value = [] self._origin_selection = [] @@ -406,10 +424,8 @@ class AssetsField(BaseClickableFrame): self._set_state_property(state) def _set_state_property(self, state): - current_value = self._name_input.property("state") - if current_value != state: - self._name_input.setProperty("state", state) - self._name_input.style().polish(self._name_input) + set_style_property(self._name_input, "state", state) + set_style_property(self._icon_btn, "state", state) def is_valid(self): """Is asset valid.""" From b17d9d23497691a4cea6174c323a2bb3cf27e870 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 15:22:32 +0200 Subject: [PATCH 42/99] fixed empty files def --- openpype/lib/attribute_definitions.py | 14 +++++++++++++- openpype/widgets/attribute_defs/files_widget.py | 6 ++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 4ea691ca08..a1f7c1e0f4 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -316,6 +316,7 @@ class FileDefItem(object): self.is_sequence = False self.template = None self.frames = [] + self.is_empty = True self.set_filenames(filenames, frames, template) @@ -323,7 +324,9 @@ class FileDefItem(object): return json.dumps(self.to_dict()) def __repr__(self): - if self.is_sequence: + if self.is_empty: + filename = "< empty >" + elif self.is_sequence: filename = self.template else: filename = self.filenames[0] @@ -335,6 +338,9 @@ class FileDefItem(object): @property def label(self): + if self.is_empty: + return None + if not self.is_sequence: return self.filenames[0] @@ -386,6 +392,8 @@ class FileDefItem(object): @property def ext(self): + if self.is_empty: + return None _, ext = os.path.splitext(self.filenames[0]) if ext: return ext @@ -393,6 +401,9 @@ class FileDefItem(object): @property def is_dir(self): + if self.is_empty: + return False + # QUESTION a better way how to define folder (in init argument?) if self.ext: return False @@ -411,6 +422,7 @@ class FileDefItem(object): if is_sequence and not template: raise ValueError("Missing template for sequence") + self.is_empty = len(filenames) == 0 self.filenames = filenames self.template = template self.frames = frames diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 924dbf7fe5..23cf8342b1 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -151,7 +151,7 @@ class FilesModel(QtGui.QStandardItemModel): item = QtGui.QStandardItem() item_id = str(uuid.uuid4()) item.setData(item_id, ITEM_ID_ROLE) - item.setData(file_item.label, ITEM_LABEL_ROLE) + item.setData(file_item.label or "< empty >", ITEM_LABEL_ROLE) item.setData(file_item.filenames, FILENAMES_ROLE) item.setData(file_item.directory, DIRPATH_ROLE) item.setData(icon_pixmap, ITEM_ICON_ROLE) @@ -512,7 +512,9 @@ class FilesWidget(QtWidgets.QFrame): return file_items if file_items: return file_items[0] - return FileDefItem.create_empty_item() + + empty_item = FileDefItem.create_empty_item() + return empty_item.to_dict() def set_filters(self, folders_allowed, exts_filter): self._files_proxy_model.set_allow_folders(folders_allowed) From 33c3d1bc4c8278690c522f1b74b1fe5a2705ed5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 15:23:22 +0200 Subject: [PATCH 43/99] resize properly families and subset names --- openpype/tools/publisher/widgets/widgets.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 1569e1a4c1..45c42e6558 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -908,11 +908,23 @@ class MultipleItemWidget(QtWidgets.QWidget): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(view) + model.rowsInserted.connect(self._on_insert) + self._view = view self._model = model self._value = [] + def _on_insert(self): + self._update_size() + + def _update_size(self): + model = self._view.model() + if model.rowCount() == 0: + return + height = self._view.sizeHintForRow(0) + self.setMaximumHeight(height + (2 * self._view.spacing())) + def showEvent(self, event): super(MultipleItemWidget, self).showEvent(event) tmp_item = None @@ -920,13 +932,15 @@ class MultipleItemWidget(QtWidgets.QWidget): # Add temp item to be able calculate maximum height of widget tmp_item = QtGui.QStandardItem("tmp") self._model.appendRow(tmp_item) - - height = self._view.sizeHintForRow(0) - self.setMaximumHeight(height + (2 * self._view.spacing())) + self._update_size() if tmp_item is not None: self._model.clear() + def resizeEvent(self, event): + super(MultipleItemWidget, self).resizeEvent(event) + self._update_size() + def set_value(self, value=None): """Set value/s of currently selected instance.""" if value is None: From 1c2b6408c4d3af491a886aa3d61c5dc7ee53a7f1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 15:28:36 +0200 Subject: [PATCH 44/99] nuke: refectory imageio settings for plugin nodes --- openpype/hosts/nuke/api/lib.py | 117 +++++++++++++++------------------ 1 file changed, 52 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index e6e61844ba..0fa5633b90 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -375,23 +375,18 @@ def add_write_node(name, **kwarg): Returns: node (obj): nuke write node """ + file_path = kwarg["path"] + knobs = kwarg["knobs"] frame_range = kwarg.get("frame_range", None) w = nuke.createNode( "Write", "name {}".format(name)) - w["file"].setValue(kwarg["file"]) + w["file"].setValue(file_path) - for k, v in kwarg.items(): - if "frame_range" in k: - continue - log.info([k, v]) - try: - w[k].setValue(v) - except KeyError as e: - log.debug(e) - continue + # finally add knob overrides + set_node_knobs_from_settings(w, knobs) if frame_range: w["use_limit"].setValue(True) @@ -501,17 +496,9 @@ def get_nuke_imageio_settings(): return get_anatomy_settings(Context.project_name)["imageio"]["nuke"] -def get_created_node_imageio_setting(**kwarg): +def get_imageio_node_setting(node_class, plugin_name, subset): ''' Get preset data for dataflow (fileType, compression, bitDepth) ''' - log.debug(kwarg) - nodeclass = kwarg.get("nodeclass", None) - creator = kwarg.get("creator", None) - subset = kwarg.get("subset", None) - - assert any([creator, nodeclass]), nuke.message( - "`{}`: Missing mandatory kwargs `host`, `cls`".format(__file__)) - imageio_nodes = get_nuke_imageio_settings()["nodes"] required_nodes = imageio_nodes["requiredNodes"] override_nodes = imageio_nodes["overrideNodes"] @@ -520,8 +507,8 @@ def get_created_node_imageio_setting(**kwarg): for node in required_nodes: log.info(node) if ( - nodeclass in node["nukeNodeClass"] - and creator in node["plugins"] + node_class in node["nukeNodeClass"] + and plugin_name in node["plugins"] ): imageio_node = node break @@ -532,10 +519,10 @@ def get_created_node_imageio_setting(**kwarg): override_imageio_node = None for onode in override_nodes: log.info(onode) - if nodeclass not in node["nukeNodeClass"]: + if node_class not in node["nukeNodeClass"]: continue - if creator not in node["plugins"]: + if plugin_name not in node["plugins"]: continue if ( @@ -726,15 +713,14 @@ def check_subsetname_exists(nodes, subset_name): def get_render_path(node): ''' Generate Render path from presets regarding avalon knob data ''' - data = {'avalon': read_avalon_data(node)} - data_preset = { - "nodeclass": data["avalon"]["family"], - "families": [data["avalon"]["families"]], - "creator": data["avalon"]["creator"], - "subset": data["avalon"]["subset"] - } + avalon_knob_data = read_avalon_data(node) + data = {'avalon': avalon_knob_data} - nuke_imageio_writes = get_created_node_imageio_setting(**data_preset) + nuke_imageio_writes = get_imageio_node_setting( + node_class=avalon_knob_data["family"], + plugin_name=avalon_knob_data["creator"], + subset=avalon_knob_data["subset"] + ) host_name = os.environ.get("AVALON_APP") data.update({ @@ -859,9 +845,20 @@ def create_write_node(name, data, input=None, prenodes=None, Return: node (obj): group node with avalon data as Knobs ''' + # group node knob overrides knob_overrides = data.get("knobs", []) - imageio_writes = get_created_node_imageio_setting(**data) + # filtering variables + plugin_name = data["creator"], + subset = data["subset"] + + # get knob settings for write node + imageio_writes = get_imageio_node_setting( + node_class=data["nodeclass"], + plugin_name=plugin_name, + subset=subset + ) + for knob in imageio_writes["knobs"]: if knob["name"] == "file_type": representation = knob["value"] @@ -893,22 +890,20 @@ def create_write_node(name, data, input=None, prenodes=None, log.warning("Path does not exist! I am creating it.") os.makedirs(os.path.dirname(fpath)) - _data = OrderedDict({ - "file": fpath - }) + _wn_props = { + "path": fpath, + "knobs": imageio_writes["knobs"] + } # adding dataflow template - log.debug("imageio_writes: `{}`".format(imageio_writes)) - for knob in imageio_writes["knobs"]: - _data[knob["name"]] = knob["value"] - - _data = fix_data_for_node_create(_data) - - log.debug("_data: `{}`".format(_data)) + log.debug("__ _wn_props: `{}`".format( + pformat(_wn_props) + )) if "frame_range" in data.keys(): - _data["frame_range"] = data.get("frame_range", None) - log.debug("_data[frame_range]: `{}`".format(_data["frame_range"])) + _wn_props["frame_range"] = data.get("frame_range", None) + log.debug("_wn_props[frame_range]: `{}`".format( + _wn_props["frame_range"])) GN = nuke.createNode("Group", "name {}".format(name)) @@ -985,7 +980,7 @@ def create_write_node(name, data, input=None, prenodes=None, # creating write node write_node = now_node = add_write_node( "inside_{}".format(name), - **_data + **_wn_props ) write_node.hideControlPanel() # connect to previous node @@ -1060,7 +1055,7 @@ def create_write_node(name, data, input=None, prenodes=None, GN[_NODE_TAB_NAME].setFlag(0) # set tile color - tile_color = _data.get("tile_color", "0xff0000ff") + tile_color = _wn_props["knobs"].get("tile_color", "0xff0000ff") GN["tile_color"].setValue(tile_color) # finally add knob overrides @@ -1414,15 +1409,11 @@ class WorkfileSettings(object): if avalon_knob_data.get("families"): families.append(avalon_knob_data.get("families")) - data_preset = { - "nodeclass": avalon_knob_data["family"], - "families": families, - "creator": avalon_knob_data["creator"], - "subset": avalon_knob_data["subset"] - } - - nuke_imageio_writes = get_created_node_imageio_setting( - **data_preset) + nuke_imageio_writes = get_imageio_node_setting( + node_class=avalon_knob_data["family"], + plugin_name=avalon_knob_data["creator"], + subset=avalon_knob_data["subset"] + ) log.debug("nuke_imageio_writes: `{}`".format(nuke_imageio_writes)) @@ -1737,17 +1728,13 @@ def get_write_node_template_attr(node): ''' # get avalon data from node - data = {"avalon": read_avalon_data(node)} - - data_preset = { - "nodeclass": data["avalon"]["family"], - "families": [data["avalon"]["families"]], - "creator": data["avalon"]["creator"], - "subset": data["avalon"]["subset"] - } - + avalon_knob_data = read_avalon_data(node) # get template data - nuke_imageio_writes = get_created_node_imageio_setting(**data_preset) + nuke_imageio_writes = get_imageio_node_setting( + node_class=avalon_knob_data["family"], + plugin_name=avalon_knob_data["creator"], + subset=avalon_knob_data["subset"] + ) # collecting correct data correct_data = OrderedDict({ From 9eb9c62318f0a00b4ceb5c21bd42c839bedaa9be Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 15:55:38 +0200 Subject: [PATCH 45/99] different asset dialog size --- .../tools/publisher/widgets/assets_widget.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index c4d3cf8b1a..46fdcc6526 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -220,9 +220,26 @@ class AssetsDialog(QtWidgets.QDialog): # - adds ability to call reset on multiple places without repeating self._soft_reset_enabled = True + self._first_show = True + self._default_height = 500 + + def _on_first_show(self): + center = self.rect().center() + size = self.size() + size.setHeight(self._default_height) + + self.resize(size) + new_pos = self.mapToGlobal(center) + new_pos.setX(new_pos.x() - int(self.width() / 2)) + new_pos.setY(new_pos.y() - int(self.height() / 2)) + self.move(new_pos) + def showEvent(self, event): """Refresh asset model on show.""" super(AssetsDialog, self).showEvent(event) + if self._first_show: + self._first_show = False + self._on_first_show() # Refresh on show self.reset(False) From 5ca7b53c11e9b9fe66bd05947d04edadd8efb0d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 15:56:01 +0200 Subject: [PATCH 46/99] fix styles --- openpype/style/style.css | 32 +++++++++++++++++---- openpype/tools/publisher/widgets/widgets.py | 6 +++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index 93b13916c1..d76d833be1 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1027,24 +1027,44 @@ VariantInputsWidget QToolButton { border-left: 1px solid {color:border}; } -#AssetNameInputButton { +#AssetNameInputWidget { background: {color:bg-inputs}; border: 1px solid {color:border}; + border-radius: 0.3em; +} + +#AssetNameInputWidget QWidget { + background: transparent; +} + +#AssetNameInputButton { border-bottom-left-radius: 0px; border-top-left-radius: 0px; padding: 0px; qproperty-iconSize: 11px 11px; + border-left: 1px solid {color:border}; + border-right: none; + border-top: none; + border-bottom: none; } -#AssetNameInputButton:disabled { - background: {color:bg-inputs-disabled}; -} + #AssetNameInput { border-bottom-right-radius: 0px; border-top-right-radius: 0px; - border-right: none; + border: none; } -#TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"], #AssetNameInputButton[state="invalid"] { +#AssetNameInputWidget:hover { + border-color: {color:border-hover}; +} +#AssetNameInputWidget:focus{ + border-color: {color:border-focus}; +} +#AssetNameInputWidget:disabled { + background: {color:bg-inputs-disabled}; +} + +#TasksCombobox[state="invalid"], #AssetNameInputWidget[state="invalid"], #AssetNameInputButton[state="invalid"] { border-color: {color:publisher:error}; } diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 45c42e6558..6c09128857 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -345,8 +345,11 @@ class AssetsField(BaseClickableFrame): def __init__(self, controller, parent): super(AssetsField, self).__init__(parent) + self.setObjectName("AssetNameInputWidget") - dialog = AssetsDialog(controller, self) + # Don't use 'self' for parent! + # - this widget has specific styles + dialog = AssetsDialog(controller, parent) name_input = ClickableLineEdit(self) name_input.setObjectName("AssetNameInput") @@ -363,6 +366,7 @@ class AssetsField(BaseClickableFrame): layout.addWidget(name_input, 1) layout.addWidget(icon_btn, 0) + # Make sure all widgets are vertically extended to highest widget for widget in ( name_input, icon_btn From c23b55f714614671a1c002ad2c5280a07d413676 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 16:09:29 +0200 Subject: [PATCH 47/99] changed default sizes of create dialog --- .../tools/publisher/widgets/create_dialog.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index bcf87dea6a..09b2b9cb30 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -283,7 +283,7 @@ class HelpButton(ClickableFrame): class CreateDialog(QtWidgets.QDialog): - default_size = (900, 500) + default_size = (1000, 560) def __init__( self, controller, asset_name=None, task_name=None, parent=None @@ -354,7 +354,6 @@ class CreateDialog(QtWidgets.QDialog): variant_hints_menu = QtWidgets.QMenu(variant_widget) variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu) - # variant_hints_btn.setMenu(variant_hints_menu) variant_layout = QtWidgets.QHBoxLayout(variant_widget) variant_layout.setContentsMargins(0, 0, 0, 0) @@ -1093,6 +1092,21 @@ class CreateDialog(QtWidgets.QDialog): self.variant_input.setProperty("state", state) self.variant_input.style().polish(self.variant_input) + def _on_first_show(self): + center = self.rect().center() + + width, height = self.default_size + self.resize(width, height) + part = int(width / 7) + self._splitter_widget.setSizes( + [part * 2, part * 2, width - (part * 4)] + ) + + new_pos = self.mapToGlobal(center) + new_pos.setX(new_pos.x() - int(self.width() / 2)) + new_pos.setY(new_pos.y() - int(self.height() / 2)) + self.move(new_pos) + def moveEvent(self, event): super(CreateDialog, self).moveEvent(event) self._last_pos = self.pos() @@ -1101,13 +1115,7 @@ class CreateDialog(QtWidgets.QDialog): super(CreateDialog, self).showEvent(event) if self._first_show: self._first_show = False - width, height = self.default_size - self.resize(width, height) - - third_size = int(width / 3) - self._splitter_widget.setSizes( - [third_size, third_size, width - (2 * third_size)] - ) + self._on_first_show() if self._last_pos is not None: self.move(self._last_pos) From d82480b1ee9d8558fd4281649b1a4c8a363fa516 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 16:17:30 +0200 Subject: [PATCH 48/99] fix variant value changes --- openpype/tools/publisher/widgets/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 6c09128857..169da717f7 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -862,6 +862,8 @@ class VariantInputWidget(PlaceholderLineEdit): self._ignore_value_change = True + self._has_value_changed = False + self._origin_value = list(variants) self._current_value = list(variants) From 06a1e484d61274a6646e6b3667e76d3525f2497e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 16:53:59 +0200 Subject: [PATCH 49/99] fix property changes --- openpype/tools/publisher/widgets/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 169da717f7..63dd0ad198 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -428,6 +428,7 @@ class AssetsField(BaseClickableFrame): self._set_state_property(state) def _set_state_property(self, state): + set_style_property(self, "state", state) set_style_property(self._name_input, "state", state) set_style_property(self._icon_btn, "state", state) From 6c838de7ffecf6a49df97fb76b0ae95a56fc3062 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 3 May 2022 16:55:58 +0200 Subject: [PATCH 50/99] Add Houdini loader to load Alembic through Alembic Archive node --- .../plugins/load/load_alembic_archive.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/load/load_alembic_archive.py diff --git a/openpype/hosts/houdini/plugins/load/load_alembic_archive.py b/openpype/hosts/houdini/plugins/load/load_alembic_archive.py new file mode 100644 index 0000000000..b960073e12 --- /dev/null +++ b/openpype/hosts/houdini/plugins/load/load_alembic_archive.py @@ -0,0 +1,75 @@ +import os +from openpype.pipeline import ( + load, + get_representation_path, +) +from openpype.hosts.houdini.api import pipeline + + +class AbcArchiveLoader(load.LoaderPlugin): + """Load Alembic as full geometry network hierarchy """ + + families = ["model", "animation", "pointcache", "gpuCache"] + label = "Load Alembic as Archive" + representations = ["abc"] + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + import hou + + # Format file name, Houdini only wants forward slashes + file_path = os.path.normpath(self.fname) + file_path = file_path.replace("\\", "/") + + # Get the root node + obj = hou.node("/obj") + + # Define node name + namespace = namespace if namespace else context["asset"]["name"] + node_name = "{}_{}".format(namespace, name) if namespace else name + + # Create an Alembic archive node + node = obj.createNode("alembicarchive", node_name=node_name) + node.moveToGoodPosition() + + # TODO: add FPS of project / asset + node.setParms({"fileName": file_path, + "channelRef": True}) + + # Apply some magic + node.parm("buildHierarchy").pressButton() + node.moveToGoodPosition() + + nodes = [node] + + self[:] = nodes + + return pipeline.containerise(node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="") + + def update(self, container, representation): + + node = container["node"] + + # Update the file path + file_path = get_representation_path(representation) + file_path = file_path.replace("\\", "/") + + # Update attributes + node.setParms({"fileName": file_path, + "representation": str(representation["_id"])}) + + # Rebuild + node.parm("buildHierarchy").pressButton() + + def remove(self, container): + + node = container["node"] + node.destroy() From 50e19bbfc075dac637e1cddc775d7cb092f54c8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 17:20:42 +0200 Subject: [PATCH 51/99] create attributes use same grid layout logic --- openpype/tools/publisher/widgets/widgets.py | 30 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 63dd0ad198..7096b9fb50 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1272,7 +1272,11 @@ class CreatorAttrsWidget(QtWidgets.QWidget): ) content_widget = QtWidgets.QWidget(self._scroll_area) - content_layout = QtWidgets.QFormLayout(content_widget) + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setColumnStretch(0, 0) + content_layout.setColumnStretch(1, 1) + + row = 0 for attr_def, attr_instances, values in result: widget = create_widget_for_attr_def(attr_def, content_widget) if attr_def.is_value_def: @@ -1283,10 +1287,28 @@ class CreatorAttrsWidget(QtWidgets.QWidget): else: widget.set_value(values, True) - label = attr_def.label or attr_def.key - content_layout.addRow(label, widget) - widget.value_changed.connect(self._input_value_changed) + expand_cols = 2 + if attr_def.is_value_def and attr_def.is_label_horizontal: + expand_cols = 1 + col_num = 2 - expand_cols + + label = attr_def.label or attr_def.key + if label: + label_widget = QtWidgets.QLabel(label, self) + content_layout.addWidget( + label_widget, row, 0, 1, expand_cols + ) + if not attr_def.is_label_horizontal: + row += 1 + + content_layout.addWidget( + widget, row, col_num, 1, expand_cols + ) + + row += 1 + + widget.value_changed.connect(self._input_value_changed) self._attr_def_id_to_instances[attr_def.id] = attr_instances self._attr_def_id_to_attr_def[attr_def.id] = attr_def From 8db65d56cc16bce55d6588a627ef5656c23a71fe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 17:31:51 +0200 Subject: [PATCH 52/99] hound fix --- openpype/tools/publisher/widgets/create_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 09b2b9cb30..9e357f3a56 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -774,7 +774,7 @@ class CreateDialog(QtWidgets.QDialog): self._help_btn.set_pos_and_size( max(0, pos_x), max(0, pos_y), - width, height + width, height ) def _on_help_btn_resize(self, height): From 289e35d68d9c7d5bd6a95a07100f7e82fcc59201 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 3 May 2022 17:33:08 +0200 Subject: [PATCH 53/99] added stretches --- openpype/widgets/attribute_defs/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 875b69acb4..b6493b80a8 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -91,6 +91,8 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget): layout.deleteLater() new_layout = QtWidgets.QGridLayout() + new_layout.setColumnStretch(0, 0) + new_layout.setColumnStretch(1, 1) self.setLayout(new_layout) def set_attr_defs(self, attr_defs): From 0dd91e8736b222651e3a158cc9d9dc266b052bd7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 22:22:49 +0200 Subject: [PATCH 54/99] Nuke: adding `formatable` to node knob keys --- openpype/hosts/nuke/api/lib.py | 33 ++++++++++++++--- .../schemas/template_nuke_knob_inputs.json | 36 +++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 0fa5633b90..475eb006ff 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1059,12 +1059,12 @@ def create_write_node(name, data, input=None, prenodes=None, GN["tile_color"].setValue(tile_color) # finally add knob overrides - set_node_knobs_from_settings(GN, knob_overrides) + set_node_knobs_from_settings(GN, knob_overrides, **kwargs) return GN -def set_node_knobs_from_settings(node, knob_settings): +def set_node_knobs_from_settings(node, knob_settings, **kwargs): """ Overriding knob values from settings Using `schema_nuke_knob_inputs` for knob type definitions. @@ -1072,13 +1072,37 @@ def set_node_knobs_from_settings(node, knob_settings): Args: node (nuke.Node): nuke node knob_settings (list): list of dict. Keys are `type`, `name`, `value` + kwargs (dict)[optional]: keys for formatable knob settings """ for knob in knob_settings: knob_type = knob["type"] knob_name = knob["name"] - knob_value = knob["value"] + if knob_name not in node.knobs(): continue + + # first deal with formatable knob settings + if knob_type == "formatable": + template = knob["template"] + to_type = knob["to_type"] + try: + _knob_value = template.format( + **kwargs + ) + except KeyError as msg: + raise KeyError(msg) + + # convert value to correct type + if to_type == "2d_vector": + knob_value = _knob_value.split(";").split(",") + else: + knob_value = _knob_value + + knob_type = to_type + + else: + knob_value = knob["value"] + if not knob_value: continue @@ -1103,9 +1127,10 @@ def set_node_knobs_from_settings(node, knob_settings): pformat(knob_settings) )) knob_value = int(knob_value, 16) - if knob_type in ["2d_vector", "3d_vector", "color"]: + elif knob_type in ["2d_vector", "3d_vector", "color"]: knob_value = [float(v) for v in knob_value] + node[knob_name].setValue(knob_value) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json index 957c684013..e6ca1e7fd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json @@ -28,6 +28,42 @@ } ] }, + { + "key": "formatable", + "label": "Formate from template", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "template", + "label": "Template", + "placeholder": "{{key}} or {{key}};{{key}}" + }, + { + "type": "enum", + "key": "to_type", + "label": "Knob type", + "enum_items": [ + { + "text": "Text" + }, + { + "number": "Number" + }, + { + "decimal_number": "Decimal number" + }, + { + "2d_vector": "2D vector" + } + ] + } + ] + }, { "key": "hex", "label": "Hexadecimal (0x)", From 3ddb46463c67025d4a46a70e4f3ee545ab99ac8f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 22:26:52 +0200 Subject: [PATCH 55/99] nuke: updating create_write_node inputs and docs --- openpype/hosts/nuke/api/lib.py | 62 ++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 475eb006ff..4170eaf1e7 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -812,39 +812,54 @@ def add_button_clear_rendered(node, path): node.addKnob(knob) -def create_write_node(name, data, input=None, prenodes=None, - review=True, linked_knobs=None, farm=True): +def create_write_node( + name, + data, + input=None, + prenodes=None, + review=True, + farm=True, + linked_knobs=None, + **kwargs +): ''' Creating write node which is group node Arguments: name (str): name of node - data (dict): data to be imprinted - input (node): selected node to connect to - prenodes (list, optional): list of lists, definitions for nodes - to be created before write - review (bool): adding review knob + data (dict): creator write instance data + input (node)[optional]: selected node to connect to + prenodes (dict)[optional]: + nodes to be created before write with dependency + review (bool)[optional]: adding review knob + farm (bool)[optional]: rendering workflow target + kwargs (dict)[optional]: additional key arguments for formating Example: - prenodes = [ - { - "nodeName": { - "class": "" # string - "knobs": [ - ("knobName": value), - ... - ], - "dependent": [ - following_node_01, - ... - ] - } + prenodes = { + "nodeName": { + "nodeclass": "Reformat", + "dependent": [ + following_node_01, + ... + ], + "knobs": [ + { + "type": "text", + "name": "knobname", + "value": "knob value" + }, + ... + ] }, ... - ] + } + Return: node (obj): group node with avalon data as Knobs ''' + prenodes = prenodes or {} + # group node knob overrides knob_overrides = data.get("knobs", []) @@ -880,7 +895,9 @@ def create_write_node(name, data, input=None, prenodes=None, # build file path to workfiles fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/") fpath = data["fpath_template"].format( - work=fdir, version=data["version"], subset=data["subset"], + work=fdir, + version=data["version"], + subset=data["subset"], frame=data["frame"], ext=representation ) @@ -919,6 +936,7 @@ def create_write_node(name, data, input=None, prenodes=None, prev_node = nuke.createNode( "Input", "name {}".format("rgba")) prev_node.hideControlPanel() + # creating pre-write nodes `prenodes` if prenodes: for node in prenodes: From 3fd5a534c6747f3b9ceb5b333112c58a7762b7d0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 22:28:05 +0200 Subject: [PATCH 56/99] nuke: adding prenodes to settings - also adding write still plugin to settings --- .../defaults/project_settings/nuke.json | 48 ++++++- .../projects_schema/schema_project_nuke.json | 125 ++++++++++++++++++ 2 files changed, 170 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 4fc6caa57e..057d950b54 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -22,10 +22,28 @@ "Main", "Mask" ], - "knobs": [] + "knobs": [], + "prenodes": { + "Reformat01": { + "nodeclass": "Reformat", + "dependent": "", + "knobs": [ + { + "type": "text", + "name": "resize", + "value": "none" + }, + { + "type": "bool", + "name": "black_outside", + "value": true + } + ] + } + } }, "CreateWritePrerender": { - "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}", + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}", "use_range_limit": true, "defaults": [ "Key01", @@ -35,7 +53,31 @@ "Part01" ], "reviewable": false, - "knobs": [] + "knobs": [], + "prenodes": {} + }, + "CreateWriteStill": { + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{ext}", + "defaults": [ + "ImageFrame{frame:0>4}", + "MPFrame{frame:0>4}", + "LayoutFrame{frame:0>4}" + ], + "knobs": [], + "prenodes": { + "FrameHold01": { + "nodeclass": "FrameHold", + "dependent": "", + "knobs": [ + { + "type": "formatable", + "name": "FrameHold", + "template": "{frame}", + "to_type": "number" + } + ] + } + } } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 29ad8e3c6c..bc572cbdc8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -114,6 +114,37 @@ "key": "knobs" } ] + }, + { + "key": "prenodes", + "label": "Pre write nodes", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "nodeclass", + "label": "Node class", + "type": "text" + }, + { + "key": "dependent", + "label": "Outside node dependency", + "type": "text" + }, + { + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] + } + ] + } } ] }, @@ -156,6 +187,100 @@ "key": "knobs" } ] + }, + { + "key": "prenodes", + "label": "Pre write nodes", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "nodeclass", + "label": "Node class", + "type": "text" + }, + { + "key": "dependent", + "label": "Outside node dependency", + "type": "text" + }, + { + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] + } + ] + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateWriteStill", + "label": "CreateWriteStill", + "is_group": true, + "children": [ + { + "type": "text", + "key": "fpath_template", + "label": "Path template" + }, + { + "type": "list", + "key": "defaults", + "label": "Subset name defaults", + "object_type": { + "type": "text" + } + }, + { + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] + }, + { + "key": "prenodes", + "label": "Pre write nodes", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "nodeclass", + "label": "Node class", + "type": "text" + }, + { + "key": "dependent", + "label": "Outside node dependency", + "type": "text" + }, + { + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] + } + ] + } } ] } From 890e10836825968b61cd1f524d7c399c394173e0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 May 2022 22:28:51 +0200 Subject: [PATCH 57/99] nuke: updating plugins with new settings with prenodes removing self.preset references --- openpype/hosts/nuke/api/plugin.py | 25 +++------ .../plugins/create/create_write_prerender.py | 21 ++++--- .../plugins/create/create_write_render.py | 43 +++++++++----- .../nuke/plugins/create/create_write_still.py | 56 +++++++++++-------- 4 files changed, 82 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 37c3633d2c..1eaccef795 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -27,9 +27,6 @@ class OpenPypeCreator(LegacyCreator): def __init__(self, *args, **kwargs): super(OpenPypeCreator, self).__init__(*args, **kwargs) - self.presets = get_current_project_settings()["nuke"]["create"].get( - self.__class__.__name__, {} - ) if check_subsetname_exists( nuke.allNodes(), self.data["subset"]): @@ -606,6 +603,7 @@ class AbstractWriteRender(OpenPypeCreator): icon = "sign-out" defaults = ["Main", "Mask"] knobs = [] + prenodes = {} def __init__(self, *args, **kwargs): super(AbstractWriteRender, self).__init__(*args, **kwargs) @@ -682,21 +680,12 @@ class AbstractWriteRender(OpenPypeCreator): self.data.update(creator_data) write_data.update(creator_data) - if self.presets.get('fpath_template'): - self.log.info("Adding template path from preset") - write_data.update( - {"fpath_template": self.presets["fpath_template"]} - ) - else: - self.log.info("Adding template path from plugin") - write_data.update({ - "fpath_template": - ("{work}/" + self.family + "s/nuke/{subset}" - "/{subset}.{frame}.{ext}")}) - - write_node = self._create_write_node(selected_node, - inputs, outputs, - write_data) + write_node = self._create_write_node( + selected_node, + inputs, + outputs, + write_data + ) # relinking to collected connections for i, input in enumerate(inputs): diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 7297f74c13..f86ed7b89e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -12,22 +12,27 @@ class CreateWritePrerender(plugin.AbstractWriteRender): n_class = "Write" family = "prerender" icon = "sign-out" + + # settings + fpath_template = "{work}/render/nuke/{subset}/{subset}.{frame}.{ext}" defaults = ["Key01", "Bg01", "Fg01", "Branch01", "Part01"] + reviewable = False + use_range_limit = True def __init__(self, *args, **kwargs): super(CreateWritePrerender, self).__init__(*args, **kwargs) def _create_write_node(self, selected_node, inputs, outputs, write_data): - reviewable = self.presets.get("reviewable") - write_node = create_write_node( + # add fpath_template + write_data["fpath_template"] = self.fpath_template + + return create_write_node( self.data["subset"], write_data, input=selected_node, - prenodes=[], - review=reviewable, - linked_knobs=["channels", "___", "first", "last", "use_limit"]) - - return write_node + review=self.reviewable, + linked_knobs=["channels", "___", "first", "last", "use_limit"] + ) def _modify_write_node(self, write_node): # open group node @@ -38,7 +43,7 @@ class CreateWritePrerender(plugin.AbstractWriteRender): w_node = n write_node.end() - if self.presets.get("use_range_limit"): + if self.use_range_limit: w_node["use_limit"].setValue(True) w_node["first"].setValue(nuke.root()["first_frame"].value()) w_node["last"].setValue(nuke.root()["last_frame"].value()) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 36a7b5c33f..43b1a9dcb4 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -12,13 +12,36 @@ class CreateWriteRender(plugin.AbstractWriteRender): n_class = "Write" family = "render" icon = "sign-out" + + # settings + fpath_template = "{work}/render/nuke/{subset}/{subset}.{frame}.{ext}" defaults = ["Main", "Mask"] - knobs = [] + prenodes = { + "Reformat01": { + "nodeclass": "Reformat", + "dependent": None, + "knobs": [ + { + "type": "text", + "name": "resize", + "value": "none" + }, + { + "type": "bool", + "name": "black_outside", + "value": True + } + ] + } + } def __init__(self, *args, **kwargs): super(CreateWriteRender, self).__init__(*args, **kwargs) def _create_write_node(self, selected_node, inputs, outputs, write_data): + # add fpath_template + write_data["fpath_template"] = self.fpath_template + # add reformat node to cut off all outside of format bounding box # get width and height try: @@ -27,23 +50,15 @@ class CreateWriteRender(plugin.AbstractWriteRender): actual_format = nuke.root().knob('format').value() width, height = (actual_format.width(), actual_format.height()) - _prenodes = [ - { - "name": "Reformat01", - "class": "Reformat", - "knobs": [ - ("resize", 0), - ("black_outside", 1), - ], - "dependent": None - } - ] - return create_write_node( self.data["subset"], write_data, input=selected_node, - prenodes=_prenodes + prenodes=self.prenodes, + **{ + "width": width, + "height": height + } ) def _modify_write_node(self, write_node): diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index d22b5eab3f..4966344b82 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -12,42 +12,52 @@ class CreateWriteStill(plugin.AbstractWriteRender): n_class = "Write" family = "still" icon = "image" + + # settings + fpath_template = "{work}/render/nuke/{subset}/{subset}.{ext}" defaults = [ - "ImageFrame{:0>4}".format(nuke.frame()), - "MPFrame{:0>4}".format(nuke.frame()), - "LayoutFrame{:0>4}".format(nuke.frame()) + "ImageFrame{frame:0>4}", + "MPFrame{frame:0>4}", + "LayoutFrame{frame:0>4}" ] + prenodes = { + "FrameHold01": { + "nodeclass": "FrameHold", + "dependent": None, + "knobs": [ + { + "type": "formatable", + "name": "first_frame", + "template": "{frame}", + "to_type": "number" + } + ] + } + } def __init__(self, *args, **kwargs): + # format defaults + new_defaults = [] + for _d in self.defaults: + new_d = _d.format(frame=nuke.frame()) + new_defaults.append(new_d) + self.defaults = new_defaults + super(CreateWriteStill, self).__init__(*args, **kwargs) def _create_write_node(self, selected_node, inputs, outputs, write_data): - # explicitly reset template to 'renders', not same as other 2 writes - write_data.update({ - "fpath_template": ( - "{work}/renders/nuke/{subset}/{subset}.{ext}")}) + # add fpath_template + write_data["fpath_template"] = self.fpath_template - _prenodes = [ - { - "name": "FrameHold01", - "class": "FrameHold", - "knobs": [ - ("first_frame", nuke.frame()) - ], - "dependent": None - } - ] - - write_node = create_write_node( + return create_write_node( self.name, write_data, input=selected_node, review=False, - prenodes=_prenodes, + prenodes=self.prenodes, farm=False, - linked_knobs=["channels", "___", "first", "last", "use_limit"]) - - return write_node + linked_knobs=["channels", "___", "first", "last", "use_limit"] + ) def _modify_write_node(self, write_node): write_node.begin() From 71e3c979768cbf24b803ef6b7995372ca4e2134a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 4 May 2022 10:45:15 +0200 Subject: [PATCH 58/99] added new session schema --- openpype/pipeline/legacy_io.py | 4 +- schema/session-3.0.json | 127 +++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 schema/session-3.0.json diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index c41406b208..b0da68f2f5 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -25,7 +25,7 @@ def install(): session = session_data_from_environment(context_keys=True) - session["schema"] = "openpype:session-2.0" + session["schema"] = "openpype:session-3.0" try: schema.validate(session) except schema.ValidationError as e: @@ -55,7 +55,7 @@ def uninstall(): def requires_install(func): @functools.wraps(func) def decorated(*args, **kwargs): - if not module._is_installed: + if not _is_installed: install() return func(*args, **kwargs) return decorated diff --git a/schema/session-3.0.json b/schema/session-3.0.json new file mode 100644 index 0000000000..4a89403592 --- /dev/null +++ b/schema/session-3.0.json @@ -0,0 +1,127 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "openpype:session-3.0", + "description": "The Avalon environment", + + "type": "object", + + "additionalProperties": true, + + "required": [ + "AVALON_PROJECT", + "AVALON_ASSET" + ], + + "properties": { + "AVALON_PROJECTS": { + "description": "Absolute path to root of project directories", + "type": "string", + "example": "/nas/projects" + }, + "AVALON_PROJECT": { + "description": "Name of project", + "type": "string", + "pattern": "^\\w*$", + "example": "Hulk" + }, + "AVALON_ASSET": { + "description": "Name of asset", + "type": "string", + "pattern": "^\\w*$", + "example": "Bruce" + }, + "AVALON_SILO": { + "description": "Name of asset group or container", + "type": "string", + "pattern": "^\\w*$", + "example": "assets" + }, + "AVALON_TASK": { + "description": "Name of task", + "type": "string", + "pattern": "^\\w*$", + "example": "modeling" + }, + "AVALON_APP": { + "description": "Name of application", + "type": "string", + "pattern": "^\\w*$", + "example": "maya2016" + }, + "AVALON_DB": { + "description": "Name of database", + "type": "string", + "pattern": "^\\w*$", + "example": "avalon", + "default": "avalon" + }, + "AVALON_LABEL": { + "description": "Nice name of Avalon, used in e.g. graphical user interfaces", + "type": "string", + "example": "Mindbender", + "default": "Avalon" + }, + "AVALON_SENTRY": { + "description": "Address to Sentry", + "type": "string", + "pattern": "^http[\\w/@:.]*$", + "example": "https://5b872b280de742919b115bdc8da076a5:8d278266fe764361b8fa6024af004a9c@logs.mindbender.com/2", + "default": null + }, + "AVALON_DEADLINE": { + "description": "Address to Deadline", + "type": "string", + "pattern": "^http[\\w/@:.]*$", + "example": "http://192.168.99.101", + "default": null + }, + "AVALON_TIMEOUT": { + "description": "Wherever there is a need for a timeout, this is the default value.", + "type": "string", + "pattern": "^[0-9]*$", + "default": "1000", + "example": "1000" + }, + "AVALON_UPLOAD": { + "description": "Boolean of whether to upload published material to central asset repository", + "type": "string", + "default": null, + "example": "True" + }, + "AVALON_USERNAME": { + "description": "Generic username", + "type": "string", + "pattern": "^\\w*$", + "default": "avalon", + "example": "myself" + }, + "AVALON_PASSWORD": { + "description": "Generic password", + "type": "string", + "pattern": "^\\w*$", + "default": "secret", + "example": "abc123" + }, + "AVALON_INSTANCE_ID": { + "description": "Unique identifier for instances in a working file", + "type": "string", + "pattern": "^[\\w.]*$", + "default": "avalon.instance", + "example": "avalon.instance" + }, + "AVALON_CONTAINER_ID": { + "description": "Unique identifier for a loaded representation in a working file", + "type": "string", + "pattern": "^[\\w.]*$", + "default": "avalon.container", + "example": "avalon.container" + }, + "AVALON_DEBUG": { + "description": "Enable debugging mode. Some applications may use this for e.g. extended verbosity or mock plug-ins.", + "type": "string", + "default": null, + "example": "True" + } + } +} From 37785895972b648cdec86efddf7acf95a921f464 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 4 May 2022 10:46:05 +0200 Subject: [PATCH 59/99] reduced session keys --- schema/session-3.0.json | 48 +---------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/schema/session-3.0.json b/schema/session-3.0.json index 4a89403592..9f785939e4 100644 --- a/schema/session-3.0.json +++ b/schema/session-3.0.json @@ -31,12 +31,6 @@ "pattern": "^\\w*$", "example": "Bruce" }, - "AVALON_SILO": { - "description": "Name of asset group or container", - "type": "string", - "pattern": "^\\w*$", - "example": "assets" - }, "AVALON_TASK": { "description": "Name of task", "type": "string", @@ -44,7 +38,7 @@ "example": "modeling" }, "AVALON_APP": { - "description": "Name of application", + "description": "Name of host", "type": "string", "pattern": "^\\w*$", "example": "maya2016" @@ -62,20 +56,6 @@ "example": "Mindbender", "default": "Avalon" }, - "AVALON_SENTRY": { - "description": "Address to Sentry", - "type": "string", - "pattern": "^http[\\w/@:.]*$", - "example": "https://5b872b280de742919b115bdc8da076a5:8d278266fe764361b8fa6024af004a9c@logs.mindbender.com/2", - "default": null - }, - "AVALON_DEADLINE": { - "description": "Address to Deadline", - "type": "string", - "pattern": "^http[\\w/@:.]*$", - "example": "http://192.168.99.101", - "default": null - }, "AVALON_TIMEOUT": { "description": "Wherever there is a need for a timeout, this is the default value.", "type": "string", @@ -83,26 +63,6 @@ "default": "1000", "example": "1000" }, - "AVALON_UPLOAD": { - "description": "Boolean of whether to upload published material to central asset repository", - "type": "string", - "default": null, - "example": "True" - }, - "AVALON_USERNAME": { - "description": "Generic username", - "type": "string", - "pattern": "^\\w*$", - "default": "avalon", - "example": "myself" - }, - "AVALON_PASSWORD": { - "description": "Generic password", - "type": "string", - "pattern": "^\\w*$", - "default": "secret", - "example": "abc123" - }, "AVALON_INSTANCE_ID": { "description": "Unique identifier for instances in a working file", "type": "string", @@ -116,12 +76,6 @@ "pattern": "^[\\w.]*$", "default": "avalon.container", "example": "avalon.container" - }, - "AVALON_DEBUG": { - "description": "Enable debugging mode. Some applications may use this for e.g. extended verbosity or mock plug-ins.", - "type": "string", - "default": null, - "example": "True" } } } From c18abc195b781dc756cec17ccf9543325c05bbb9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 4 May 2022 12:08:56 +0200 Subject: [PATCH 60/99] nuke: extracting prenodes creation and fixing `dependency` linking --- openpype/hosts/nuke/api/lib.py | 98 +++++++++++++++------------------- 1 file changed, 42 insertions(+), 56 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 4170eaf1e7..fc1ec7ec3d 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -812,6 +812,47 @@ def add_button_clear_rendered(node, path): node.addKnob(knob) +def create_prenodes(prev_node, nodes_setting, **kwargs): + last_node = None + for_dependency = {} + for name, node in nodes_setting.items(): + # get attributes + nodeclass = node["nodeclass"] + knobs = node["knobs"] + + # create node + now_node = nuke.createNode( + nodeclass, "name {}".format(name)) + now_node.hideControlPanel() + + # add for dependency linking + for_dependency[name] = { + "node": now_node, + "dependent": node["dependent"] + } + + # add data to knob + set_node_knobs_from_settings(now_node, knobs, **kwargs) + + # switch actual node to previous + last_node = now_node + + for _node_name, node_prop in for_dependency.items(): + if not node_prop["dependent"]: + node_prop["node"].setInput( + 0, prev_node) + elif node_prop["dependent"] in for_dependency: + _prev_node = for_dependency[node_prop["dependent"]] + node_prop["node"].setInput( + 0, _prev_node) + else: + log.warning("Dependency has wrong name of node: {}".format( + node_prop + )) + + return last_node + + def create_write_node( name, data, @@ -938,62 +979,7 @@ def create_write_node( prev_node.hideControlPanel() # creating pre-write nodes `prenodes` - if prenodes: - for node in prenodes: - # get attributes - pre_node_name = node["name"] - klass = node["class"] - knobs = node["knobs"] - dependent = node["dependent"] - - # create node - now_node = nuke.createNode( - klass, "name {}".format(pre_node_name)) - now_node.hideControlPanel() - - # add data to knob - for _knob in knobs: - knob, value = _knob - try: - now_node[knob].value() - except NameError: - log.warning( - "knob `{}` does not exist on node `{}`".format( - knob, now_node["name"].value() - )) - continue - - if not knob and not value: - continue - - log.info((knob, value)) - - if isinstance(value, str): - if "[" in value: - now_node[knob].setExpression(value) - else: - now_node[knob].setValue(value) - - # connect to previous node - if dependent: - if isinstance(dependent, (tuple or list)): - for i, node_name in enumerate(dependent): - input_node = nuke.createNode( - "Input", "name {}".format(node_name)) - input_node.hideControlPanel() - now_node.setInput(1, input_node) - - elif isinstance(dependent, str): - input_node = nuke.createNode( - "Input", "name {}".format(node_name)) - input_node.hideControlPanel() - now_node.setInput(0, input_node) - - else: - now_node.setInput(0, prev_node) - - # switch actual node to previous - prev_node = now_node + create_prenodes(prev_node, prenodes, **kwargs) # creating write node write_node = now_node = add_write_node( From bc07da3de252167ebf7c604ef7dd2fd1245414d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 4 May 2022 17:02:51 +0200 Subject: [PATCH 61/99] nuke: fixing create write still --- .../nuke/plugins/create/create_write_still.py | 22 +++++++++---------- .../defaults/project_settings/nuke.json | 8 +++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 4966344b82..0b1a942e0e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -2,6 +2,10 @@ import nuke from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import create_write_node +from openpype.api import ( + Logger +) +log = Logger.get_logger(__name__) class CreateWriteStill(plugin.AbstractWriteRender): @@ -16,9 +20,9 @@ class CreateWriteStill(plugin.AbstractWriteRender): # settings fpath_template = "{work}/render/nuke/{subset}/{subset}.{ext}" defaults = [ - "ImageFrame{frame:0>4}", - "MPFrame{frame:0>4}", - "LayoutFrame{frame:0>4}" + "ImageFrame", + "MPFrame", + "LayoutFrame" ] prenodes = { "FrameHold01": { @@ -36,13 +40,6 @@ class CreateWriteStill(plugin.AbstractWriteRender): } def __init__(self, *args, **kwargs): - # format defaults - new_defaults = [] - for _d in self.defaults: - new_d = _d.format(frame=nuke.frame()) - new_defaults.append(new_d) - self.defaults = new_defaults - super(CreateWriteStill, self).__init__(*args, **kwargs) def _create_write_node(self, selected_node, inputs, outputs, write_data): @@ -56,7 +53,10 @@ class CreateWriteStill(plugin.AbstractWriteRender): review=False, prenodes=self.prenodes, farm=False, - linked_knobs=["channels", "___", "first", "last", "use_limit"] + linked_knobs=["channels", "___", "first", "last", "use_limit"], + **{ + "frame": nuke.frame() + } ) def _modify_write_node(self, write_node): diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 057d950b54..128d440732 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -59,9 +59,9 @@ "CreateWriteStill": { "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{ext}", "defaults": [ - "ImageFrame{frame:0>4}", - "MPFrame{frame:0>4}", - "LayoutFrame{frame:0>4}" + "ImageFrame", + "MPFrame", + "LayoutFrame" ], "knobs": [], "prenodes": { @@ -71,7 +71,7 @@ "knobs": [ { "type": "formatable", - "name": "FrameHold", + "name": "first_frame", "template": "{frame}", "to_type": "number" } From a6ff0174b0a8ad0ecec5f230d860d02bd914f903 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 4 May 2022 17:03:46 +0200 Subject: [PATCH 62/99] Nuke: fixing lib functions for prenodes and others --- openpype/hosts/nuke/api/lib.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index fc1ec7ec3d..6d6a988b44 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -842,7 +842,7 @@ def create_prenodes(prev_node, nodes_setting, **kwargs): node_prop["node"].setInput( 0, prev_node) elif node_prop["dependent"] in for_dependency: - _prev_node = for_dependency[node_prop["dependent"]] + _prev_node = for_dependency[node_prop["dependent"]]["node"] node_prop["node"].setInput( 0, _prev_node) else: @@ -905,7 +905,7 @@ def create_write_node( knob_overrides = data.get("knobs", []) # filtering variables - plugin_name = data["creator"], + plugin_name = data["creator"] subset = data["subset"] # get knob settings for write node @@ -979,7 +979,9 @@ def create_write_node( prev_node.hideControlPanel() # creating pre-write nodes `prenodes` - create_prenodes(prev_node, prenodes, **kwargs) + last_prenode = create_prenodes(prev_node, prenodes, **kwargs) + if last_prenode: + prev_node = last_prenode # creating write node write_node = now_node = add_write_node( @@ -1059,8 +1061,14 @@ def create_write_node( GN[_NODE_TAB_NAME].setFlag(0) # set tile color - tile_color = _wn_props["knobs"].get("tile_color", "0xff0000ff") - GN["tile_color"].setValue(tile_color) + tile_color = next( + iter( + k["value"] for k in _wn_props["knobs"] + if "tile_color" in k["name"] + ), "0xff0000ff" + ) + GN["tile_color"].setValue( + int(tile_color, 16)) # finally add knob overrides set_node_knobs_from_settings(GN, knob_overrides, **kwargs) @@ -1079,6 +1087,7 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs): kwargs (dict)[optional]: keys for formatable knob settings """ for knob in knob_settings: + log.debug("__ knob: {}".format(pformat(knob))) knob_type = knob["type"] knob_name = knob["name"] @@ -1093,7 +1102,9 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs): _knob_value = template.format( **kwargs ) + log.debug("__ knob_value0: {}".format(_knob_value)) except KeyError as msg: + log.warning("__ msg: {}".format(msg)) raise KeyError(msg) # convert value to correct type From cb0ca1c220a231b117c3f6d35b30a8f9fb22c71b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 4 May 2022 17:38:17 +0200 Subject: [PATCH 63/99] '_is_installed' is accessed from 'module' --- openpype/pipeline/legacy_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index b0da68f2f5..c8e7e79600 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -55,7 +55,7 @@ def uninstall(): def requires_install(func): @functools.wraps(func) def decorated(*args, **kwargs): - if not _is_installed: + if not module._is_installed: install() return func(*args, **kwargs) return decorated From 2ec9bcfca17dcfdc8ce435c86e798475283b8f1b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 4 May 2022 17:43:17 +0200 Subject: [PATCH 64/99] reduced duplicated code when OP version is not allowed --- start.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/start.py b/start.py index cd1d95dd8f..89c5c98d27 100644 --- a/start.py +++ b/start.py @@ -942,6 +942,17 @@ def _boot_print_versions(use_staging, local_version, openpype_root): list_versions(openpype_versions, local_version) +def _boot_handle_missing_version(local_version, use_staging, message): + _print(message) + if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1": + openpype_versions = bootstrap.find_openpype( + include_zips=True, staging=use_staging + ) + list_versions(openpype_versions, local_version) + else: + igniter.show_message_dialog("Version not found", message) + + def boot(): """Bootstrap OpenPype.""" @@ -1034,15 +1045,7 @@ def boot(): try: version_path = _find_frozen_openpype(use_version, use_staging) except OpenPypeVersionNotFound as exc: - message = str(exc) - _print(message) - if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1": - openpype_versions = bootstrap.find_openpype( - include_zips=True, staging=use_staging - ) - list_versions(openpype_versions, local_version) - else: - igniter.show_message_dialog("Version not found", message) + _boot_handle_missing_version(local_version, use_staging, str(exc)) sys.exit(1) except RuntimeError as e: @@ -1061,15 +1064,7 @@ def boot(): version_path = _bootstrap_from_code(use_version, use_staging) except OpenPypeVersionNotFound as exc: - message = str(exc) - _print(message) - if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1": - openpype_versions = bootstrap.find_openpype( - include_zips=True, staging=use_staging - ) - list_versions(openpype_versions, local_version) - else: - igniter.show_message_dialog("Version not found", message) + _boot_handle_missing_version(local_version, use_staging, str(exc)) sys.exit(1) # set this to point either to `python` from venv in case of live code From f3cf21ebb4c4486098a2c9aea80d50656786e46b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 4 May 2022 17:45:54 +0200 Subject: [PATCH 65/99] avoid duplicated calls --- start.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/start.py b/start.py index 89c5c98d27..6e339fabab 100644 --- a/start.py +++ b/start.py @@ -911,17 +911,11 @@ def _boot_validate_versions(use_version, local_version): sys.exit(1) # print result - result = bootstrap.validate_openpype_version( - bootstrap.get_version_path_from_list( - use_version, openpype_versions)) - - _print("{}{}".format( - ">>> " if result[0] else "!!! ", - bootstrap.validate_openpype_version( - bootstrap.get_version_path_from_list( - use_version, openpype_versions) - )[1]) + version_path = bootstrap.get_version_path_from_list( + use_version, openpype_versions ) + valid, message = bootstrap.validate_openpype_version(version_path) + _print("{}{}".format(">>> " if valid else "!!! ", message)) def _boot_print_versions(use_staging, local_version, openpype_root): From 0c8b7e303a6eefcfc03986aafd6264c268567c59 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 4 May 2022 18:03:16 +0200 Subject: [PATCH 66/99] nuke: improving add write node function fixing imagio override nodes --- openpype/hosts/nuke/api/lib.py | 91 +++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 6d6a988b44..2d205d0d39 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -365,7 +365,7 @@ def fix_data_for_node_create(data): return data -def add_write_node(name, **kwarg): +def add_write_node(name, file_path, knobs, **kwarg): """Adding nuke write node Arguments: @@ -375,8 +375,6 @@ def add_write_node(name, **kwarg): Returns: node (obj): nuke write node """ - file_path = kwarg["path"] - knobs = kwarg["knobs"] frame_range = kwarg.get("frame_range", None) w = nuke.createNode( @@ -386,7 +384,7 @@ def add_write_node(name, **kwarg): w["file"].setValue(file_path) # finally add knob overrides - set_node_knobs_from_settings(w, knobs) + set_node_knobs_from_settings(w, knobs, **kwarg) if frame_range: w["use_limit"].setValue(True) @@ -501,7 +499,6 @@ def get_imageio_node_setting(node_class, plugin_name, subset): ''' imageio_nodes = get_nuke_imageio_settings()["nodes"] required_nodes = imageio_nodes["requiredNodes"] - override_nodes = imageio_nodes["overrideNodes"] imageio_node = None for node in required_nodes: @@ -515,14 +512,34 @@ def get_imageio_node_setting(node_class, plugin_name, subset): log.debug("__ imageio_node: {}".format(imageio_node)) + # find overrides and update knobs with them + get_imageio_node_override_setting( + node_class, + plugin_name, + subset, + imageio_node["knobs"] + ) + + log.info("ImageIO node: {}".format(imageio_node)) + return imageio_node + + +def get_imageio_node_override_setting( + node_class, plugin_name, subset, knobs_settings +): + ''' Get imageio node overrides from settings + ''' + imageio_nodes = get_nuke_imageio_settings()["nodes"] + override_nodes = imageio_nodes["overrideNodes"] + # find matching override node override_imageio_node = None for onode in override_nodes: log.info(onode) - if node_class not in node["nukeNodeClass"]: + if node_class not in onode["nukeNodeClass"]: continue - if plugin_name not in node["plugins"]: + if plugin_name not in onode["plugins"]: continue if ( @@ -538,10 +555,10 @@ def get_imageio_node_setting(node_class, plugin_name, subset): # add overrides to imageio_node if override_imageio_node: # get all knob names in imageio_node - knob_names = [k["name"] for k in imageio_node["knobs"]] + knob_names = [k["name"] for k in knobs_settings] for oknob in override_imageio_node["knobs"]: - for knob in imageio_node["knobs"]: + for knob in knobs_settings: # override matching knob name if oknob["name"] == knob["name"]: log.debug( @@ -550,7 +567,7 @@ def get_imageio_node_setting(node_class, plugin_name, subset): )) if not oknob["value"]: # remove original knob if no value found in oknob - imageio_node["knobs"].remove(knob) + knobs_settings.remove(knob) else: # override knob value with oknob's knob["value"] = oknob["value"] @@ -559,11 +576,10 @@ def get_imageio_node_setting(node_class, plugin_name, subset): if oknob["name"] not in knob_names: log.debug( "_ adding knob: `{}`".format(oknob)) - imageio_node["knobs"].append(oknob) + knobs_settings.append(oknob) knob_names.append(oknob["name"]) - log.info("ImageIO node: {}".format(imageio_node)) - return imageio_node + return knobs_settings def get_imageio_input_colorspace(filename): @@ -812,7 +828,13 @@ def add_button_clear_rendered(node, path): node.addKnob(knob) -def create_prenodes(prev_node, nodes_setting, **kwargs): +def create_prenodes( + prev_node, + nodes_setting, + plugin_name=None, + subset=None, + **kwargs +): last_node = None for_dependency = {} for name, node in nodes_setting.items(): @@ -831,6 +853,15 @@ def create_prenodes(prev_node, nodes_setting, **kwargs): "dependent": node["dependent"] } + if all([plugin_name, subset]): + # find imageio overrides + get_imageio_node_override_setting( + now_node.Class(), + plugin_name, + subset, + knobs + ) + # add data to knob set_node_knobs_from_settings(now_node, knobs, **kwargs) @@ -902,7 +933,7 @@ def create_write_node( prenodes = prenodes or {} # group node knob overrides - knob_overrides = data.get("knobs", []) + knob_overrides = data.pop("knobs", []) # filtering variables plugin_name = data["creator"] @@ -948,21 +979,6 @@ def create_write_node( log.warning("Path does not exist! I am creating it.") os.makedirs(os.path.dirname(fpath)) - _wn_props = { - "path": fpath, - "knobs": imageio_writes["knobs"] - } - - # adding dataflow template - log.debug("__ _wn_props: `{}`".format( - pformat(_wn_props) - )) - - if "frame_range" in data.keys(): - _wn_props["frame_range"] = data.get("frame_range", None) - log.debug("_wn_props[frame_range]: `{}`".format( - _wn_props["frame_range"])) - GN = nuke.createNode("Group", "name {}".format(name)) prev_node = None @@ -979,14 +995,22 @@ def create_write_node( prev_node.hideControlPanel() # creating pre-write nodes `prenodes` - last_prenode = create_prenodes(prev_node, prenodes, **kwargs) + last_prenode = create_prenodes( + prev_node, + prenodes, + plugin_name, + subset, + **kwargs + ) if last_prenode: prev_node = last_prenode # creating write node write_node = now_node = add_write_node( "inside_{}".format(name), - **_wn_props + fpath, + imageio_writes["knobs"], + **data ) write_node.hideControlPanel() # connect to previous node @@ -1063,7 +1087,7 @@ def create_write_node( # set tile color tile_color = next( iter( - k["value"] for k in _wn_props["knobs"] + k["value"] for k in imageio_writes["knobs"] if "tile_color" in k["name"] ), "0xff0000ff" ) @@ -1145,7 +1169,6 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs): elif knob_type in ["2d_vector", "3d_vector", "color"]: knob_value = [float(v) for v in knob_value] - node[knob_name].setValue(knob_value) From 3744b21266258fdd69814807a4152e268dca3d27 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 5 May 2022 09:46:51 +0200 Subject: [PATCH 67/99] fix the output dir --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 8f776a3371..7bf68f51ee 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -266,6 +266,7 @@ def get_renderer_variables(renderlayer, root): filename_prefix = cmds.getAttr(prefix_attr) return {"ext": extension, + "renderer": renderer, "filename_prefix": filename_prefix, "padding": padding, "filename_0": filename_0} @@ -440,7 +441,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): output_filename_0 = filename_0 - dirname = os.path.dirname(output_filename_0) + if render_variables["renderer"] == "renderman": + dirname = os.path.dirname(output_filename_0) # Create render folder ---------------------------------------------- try: From 1997eaf0f4bfa1a47bf849afed1d6c0f313fef5a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 May 2022 11:26:53 +0200 Subject: [PATCH 68/99] Remove project_settings.py --- openpype/widgets/project_settings.py | 494 --------------------------- 1 file changed, 494 deletions(-) delete mode 100644 openpype/widgets/project_settings.py diff --git a/openpype/widgets/project_settings.py b/openpype/widgets/project_settings.py deleted file mode 100644 index 687e17b3bf..0000000000 --- a/openpype/widgets/project_settings.py +++ /dev/null @@ -1,494 +0,0 @@ -import os -import getpass -import platform - -from Qt import QtCore, QtGui, QtWidgets - -from openpype import style -import ftrack_api - - -class Project_name_getUI(QtWidgets.QWidget): - ''' - Project setting ui: here all the neceserry ui widgets are created - they are going to be used i later proces for dynamic linking of project - in list to project's attributes - ''' - - def __init__(self, parent=None): - super(Project_name_getUI, self).__init__(parent) - - self.platform = platform.system() - self.new_index = 0 - # get projects from ftrack - self.session = ftrack_api.Session() - self.projects_from_ft = self.session.query( - 'Project where status is active') - self.disks_from_ft = self.session.query('Disk') - self.schemas_from_ft = self.session.query('ProjectSchema') - self.projects = self._get_projects_ftrack() - - # define window geometry - self.setWindowTitle('Set project attributes') - self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - self.resize(550, 340) - self.setStyleSheet(style.load_stylesheet()) - - # define disk combobox widget - self.disks = self._get_all_disks() - self.disk_combobox_label = QtWidgets.QLabel('Destination storage:') - self.disk_combobox = QtWidgets.QComboBox() - - # define schema combobox widget - self.schemas = self._get_all_schemas() - self.schema_combobox_label = QtWidgets.QLabel('Project schema:') - self.schema_combobox = QtWidgets.QComboBox() - - # define fps widget - self.fps_label = QtWidgets.QLabel('Fps:') - self.fps_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.fps = QtWidgets.QLineEdit() - - # define project dir widget - self.project_dir_label = QtWidgets.QLabel('Project dir:') - self.project_dir_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.project_dir = QtWidgets.QLineEdit() - - self.project_path_label = QtWidgets.QLabel( - 'Project_path (if not then created):') - self.project_path_label.setAlignment( - QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - project_path_font = QtGui.QFont( - "Helvetica [Cronyx]", 12, QtGui.QFont.Bold) - self.project_path = QtWidgets.QLabel() - self.project_path.setObjectName('nom_plan_label') - self.project_path.setStyleSheet( - 'QtWidgets.QLabel#nom_plan_label {color: red}') - self.project_path.setAlignment( - QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) - self.project_path.setFont(project_path_font) - - # define handles widget - self.handles_label = QtWidgets.QLabel('Handles:') - self.handles_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.handles = QtWidgets.QLineEdit() - - # define resolution widget - self.resolution_w_label = QtWidgets.QLabel('W:') - self.resolution_w = QtWidgets.QLineEdit() - self.resolution_h_label = QtWidgets.QLabel('H:') - self.resolution_h = QtWidgets.QLineEdit() - - devider = QtWidgets.QFrame() - # devider.Shape(QFrame.HLine) - devider.setFrameShape(QtWidgets.QFrame.HLine) - devider.setFrameShadow(QtWidgets.QFrame.Sunken) - - self.generate_lines() - - # define push buttons - self.set_pushbutton = QtWidgets.QPushButton('Set project') - self.cancel_pushbutton = QtWidgets.QPushButton('Cancel') - - # definition of layouts - ############################################ - action_layout = QtWidgets.QHBoxLayout() - action_layout.addWidget(self.set_pushbutton) - action_layout.addWidget(self.cancel_pushbutton) - - # schema property - schema_layout = QtWidgets.QGridLayout() - schema_layout.addWidget(self.schema_combobox, 0, 1) - schema_layout.addWidget(self.schema_combobox_label, 0, 0) - - # storage property - storage_layout = QtWidgets.QGridLayout() - storage_layout.addWidget(self.disk_combobox, 0, 1) - storage_layout.addWidget(self.disk_combobox_label, 0, 0) - - # fps property - fps_layout = QtWidgets.QGridLayout() - fps_layout.addWidget(self.fps, 1, 1) - fps_layout.addWidget(self.fps_label, 1, 0) - - # project dir property - project_dir_layout = QtWidgets.QGridLayout() - project_dir_layout.addWidget(self.project_dir, 1, 1) - project_dir_layout.addWidget(self.project_dir_label, 1, 0) - - # project path property - project_path_layout = QtWidgets.QGridLayout() - spacer_1_item = QtWidgets.QSpacerItem(10, 10) - project_path_layout.addItem(spacer_1_item, 0, 1) - project_path_layout.addWidget(self.project_path_label, 1, 1) - project_path_layout.addWidget(self.project_path, 2, 1) - spacer_2_item = QtWidgets.QSpacerItem(20, 20) - project_path_layout.addItem(spacer_2_item, 3, 1) - - # handles property - handles_layout = QtWidgets.QGridLayout() - handles_layout.addWidget(self.handles, 1, 1) - handles_layout.addWidget(self.handles_label, 1, 0) - - # resolution property - resolution_layout = QtWidgets.QGridLayout() - resolution_layout.addWidget(self.resolution_w_label, 1, 1) - resolution_layout.addWidget(self.resolution_w, 2, 1) - resolution_layout.addWidget(self.resolution_h_label, 1, 2) - resolution_layout.addWidget(self.resolution_h, 2, 2) - - # form project property layout - p_layout = QtWidgets.QGridLayout() - p_layout.addLayout(storage_layout, 1, 0) - p_layout.addLayout(schema_layout, 2, 0) - p_layout.addLayout(project_dir_layout, 3, 0) - p_layout.addLayout(fps_layout, 4, 0) - p_layout.addLayout(handles_layout, 5, 0) - p_layout.addLayout(resolution_layout, 6, 0) - p_layout.addWidget(devider, 7, 0) - spacer_item = QtWidgets.QSpacerItem( - 150, - 40, - QtWidgets.QSizePolicy.Minimum, - QtWidgets.QSizePolicy.Expanding - ) - p_layout.addItem(spacer_item, 8, 0) - - # form with list to one layout with project property - list_layout = QtWidgets.QGridLayout() - list_layout.addLayout(p_layout, 1, 0) - list_layout.addWidget(self.listWidget, 1, 1) - - root_layout = QtWidgets.QVBoxLayout() - root_layout.addLayout(project_path_layout) - root_layout.addWidget(devider) - root_layout.addLayout(list_layout) - root_layout.addLayout(action_layout) - - self.setLayout(root_layout) - - def generate_lines(self): - ''' - Will generate lines of project list - ''' - - self.listWidget = QtWidgets.QListWidget() - for self.index, p in enumerate(self.projects): - item = QtWidgets.QListWidgetItem("{full_name}".format(**p)) - # item.setSelected(False) - self.listWidget.addItem(item) - print(self.listWidget.indexFromItem(item)) - # self.listWidget.setCurrentItem(self.listWidget.itemFromIndex(1)) - - # add options to schemas widget - self.schema_combobox.addItems(self.schemas) - - # add options to disk widget - self.disk_combobox.addItems(self.disks) - - # populate content of project info widgets - self.projects[1] = self._fill_project_attributes_widgets(p, None) - - def _fill_project_attributes_widgets(self, p=None, index=None): - ''' - will generate actual informations wich are saved on ftrack - ''' - - if index is None: - self.new_index = 1 - - if not p: - pass - # change schema selection - for i, schema in enumerate(self.schemas): - if p['project_schema']['name'] in schema: - break - self.schema_combobox.setCurrentIndex(i) - - disk_name, disk_path = self._build_disk_path() - for i, disk in enumerate(self.disks): - if disk_name in disk: - break - # change disk selection - self.disk_combobox.setCurrentIndex(i) - - # change project_dir selection - if "{root}".format(**p): - self.project_dir.setPlaceholderText("{root}".format(**p)) - else: - print("not root so it was replaced with name") - self.project_dir.setPlaceholderText("{name}".format(**p)) - p['root'] = p['name'] - - # set project path to show where it will be created - self.project_path.setText( - os.path.join(self.disks[i].split(' ')[-1], - self.project_dir.text())) - - # change fps selection - self.fps.setPlaceholderText("{custom_attributes[fps]}".format(**p)) - - # change handles selection - self.handles.setPlaceholderText( - "{custom_attributes[handles]}".format(**p)) - - # change resolution selection - self.resolution_w.setPlaceholderText( - "{custom_attributes[resolution_width]}".format(**p)) - self.resolution_h.setPlaceholderText( - "{custom_attributes[resolution_height]}".format(**p)) - - self.update_disk() - - return p - - def fix_project_path_literals(self, dir): - return dir.replace(' ', '_').lower() - - def update_disk(self): - disk = self.disk_combobox.currentText().split(' ')[-1] - - dir = self.project_dir.text() - if not dir: - dir = "{root}".format(**self.projects[self.new_index]) - self.projects[self.new_index]['project_path'] = os.path.normpath( - self.fix_project_path_literals(os.path.join(disk, dir))) - else: - self.projects[self.new_index]['project_path'] = os.path.normpath( - self.fix_project_path_literals(os.path.join(disk, dir))) - - self.projects[self.new_index]['disk'] = self.disks_from_ft[ - self.disk_combobox.currentIndex()] - self.projects[self.new_index]['disk_id'] = self.projects[ - self.new_index]['disk']['id'] - - # set project path to show where it will be created - self.project_path.setText( - self.projects[self.new_index]['project_path']) - - def update_resolution(self): - # update all values in resolution - if self.resolution_w.text(): - self.projects[self.new_index]['custom_attributes'][ - "resolutionWidth"] = int(self.resolution_w.text()) - if self.resolution_h.text(): - self.projects[self.new_index]['custom_attributes'][ - "resolutionHeight"] = int(self.resolution_h.text()) - - def _update_attributes_by_list_selection(self): - # generate actual selection index - self.new_index = self.listWidget.currentRow() - self.project_dir.setText('') - self.fps.setText('') - self.handles.setText('') - self.resolution_w.setText('') - self.resolution_h.setText('') - - # update project properities widgets and write changes - # into project dictionaries - self.projects[self.new_index] = self._fill_project_attributes_widgets( - self.projects[self.new_index], self.new_index) - - self.update_disk() - - def _build_disk_path(self): - if self.platform == "Windows": - print(self.projects[self.index].keys()) - print(self.projects[self.new_index]['disk']) - return self.projects[self.new_index]['disk'][ - 'name'], self.projects[self.new_index]['disk']['windows'] - else: - return self.projects[self.new_index]['disk'][ - 'name'], self.projects[self.new_index]['disk']['unix'] - - def _get_all_schemas(self): - schemas_list = [] - - for s in self.schemas_from_ft: - # print d.keys() - # if 'Pokus' in s['name']: - # continue - schemas_list.append('{}'.format(s['name'])) - print("\nschemas in ftrack: {}\n".format(schemas_list)) - return schemas_list - - def _get_all_disks(self): - disks_list = [] - for d in self.disks_from_ft: - # print d.keys() - if self.platform == "Windows": - if 'Local drive' in d['name']: - d['windows'] = os.path.join(d['windows'], - os.getenv('USERNAME') - or os.getenv('USER') - or os.getenv('LOGNAME')) - disks_list.append('"{}" at {}'.format(d['name'], d['windows'])) - else: - if 'Local drive' in d['name']: - d['unix'] = os.path.join(d['unix'], getpass.getuser()) - disks_list.append('"{}" at {}'.format(d['name'], d['unix'])) - return disks_list - - def _get_projects_ftrack(self): - - projects_lst = [] - for project in self.projects_from_ft: - # print project.keys() - projects_dict = {} - - for k in project.keys(): - ''' # TODO: delete this in production version ''' - - # if 'test' not in project['name']: - # continue - - # print '{}: {}\n'.format(k, project[k]) - - if '_link' == k: - # print project[k] - content = project[k] - for kc in content[0].keys(): - if content[0]['name']: - content[0][kc] = content[0][kc].encode( - 'ascii', 'ignore').decode('ascii') - print('{}: {}\n'.format(kc, content[0][kc])) - projects_dict[k] = content - print(project[k]) - print(projects_dict[k]) - elif 'root' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - elif 'disk' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - elif 'name' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k].encode( - 'ascii', 'ignore').decode('ascii') - elif 'disk_id' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - elif 'id' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - elif 'full_name' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k].encode( - 'ascii', 'ignore').decode('ascii') - elif 'project_schema_id' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - elif 'project_schema' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - elif 'custom_attributes' == k: - print('{}: {}\n'.format(k, project[k])) - projects_dict[k] = project[k] - else: - pass - - if projects_dict: - projects_lst.append(projects_dict) - - return projects_lst - - -class Project_name_get(Project_name_getUI): - def __init__(self, parent=None): - super(Project_name_get, self).__init__(parent) - # self.input_project_name.textChanged.connect(self.input_project_name.placeholderText) - - self.set_pushbutton.clicked.connect(lambda: self.execute()) - self.cancel_pushbutton.clicked.connect(self.close) - - self.listWidget.itemSelectionChanged.connect( - self._update_attributes_by_list_selection) - self.disk_combobox.currentIndexChanged.connect(self.update_disk) - self.schema_combobox.currentIndexChanged.connect(self.update_schema) - self.project_dir.textChanged.connect(self.update_disk) - self.fps.textChanged.connect(self.update_fps) - self.handles.textChanged.connect(self.update_handles) - self.resolution_w.textChanged.connect(self.update_resolution) - self.resolution_h.textChanged.connect(self.update_resolution) - - def update_handles(self): - self.projects[self.new_index]['custom_attributes']['handles'] = int( - self.handles.text()) - - def update_fps(self): - self.projects[self.new_index]['custom_attributes']['fps'] = int( - self.fps.text()) - - def update_schema(self): - self.projects[self.new_index]['project_schema'] = self.schemas_from_ft[ - self.schema_combobox.currentIndex()] - self.projects[self.new_index]['project_schema_id'] = self.projects[ - self.new_index]['project_schema']['id'] - - def execute(self): - # import ft_utils - # import hiero - # get the project which has been selected - print("well and what") - # set the project as context and create entity - # entity is task created with the name of user which is creating it - - # get the project_path and create dir if there is not any - print(self.projects[self.new_index]['project_path'].replace( - self.disk_combobox.currentText().split(' ')[-1].lower(), '')) - - # get the schema and recreate a starting project regarding the selection - # set_hiero_template(project_schema=self.projects[self.new_index][ - # 'project_schema']['name']) - - # set all project properities - # project = hiero.core.Project() - # project.setFramerate( - # int(self.projects[self.new_index]['custom_attributes']['fps'])) - # project.projectRoot() - # print 'handles: {}'.format(self.projects[self.new_index]['custom_attributes']['handles']) - # print 'resolution_width: {}'.format(self.projects[self.new_index]['custom_attributes']["resolutionWidth"]) - # print 'resolution_width: {}'.format(self.projects[self.new_index]['custom_attributes']["resolutionHeight"]) - # print "<< {}".format(self.projects[self.new_index]) - - # get path for the hrox file - # root = context.data('ftrackData')['Project']['root'] - # hrox_script_path = ft_utils.getPathsYaml(taskid, templateList=templates, root=root) - - # save the hrox into the correct path - self.session.commit() - self.close() - -# -# def set_hiero_template(project_schema=None): -# import hiero -# hiero.core.closeAllProjects() -# hiero_plugin_path = [ -# p for p in os.environ['HIERO_PLUGIN_PATH'].split(';') -# if 'hiero_plugin_path' in p -# ][0] -# path = os.path.normpath( -# os.path.join(hiero_plugin_path, 'Templates', project_schema + '.hrox')) -# print('---> path to template: {}'.format(path)) -# return hiero.core.openProject(path) - - -# def set_out_ft_session(): -# session = ftrack_api.Session() -# projects_to_ft = session.query('Project where status is active') - - -def main(): - import sys - app = QtWidgets.QApplication(sys.argv) - panel = Project_name_get() - panel.show() - - sys.exit(app.exec_()) - - -if __name__ == "__main__": - main() From fe0978a8b2932ade893be5f3ef95951bb869f955 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 May 2022 16:19:26 +0200 Subject: [PATCH 69/99] Tweak grammar --- openpype/lib/applications.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index b52da52dc9..a84ff990b2 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1012,8 +1012,8 @@ class ApplicationLaunchContext: self.log.debug("Discovery of launch hooks started.") paths = self.paths_to_launch_hooks() - self.log.debug("Paths where will look for launch hooks:{}".format( - "\n- ".join(paths) + self.log.debug("Paths searched for launch hooks:\n{}".format( + "\n".join("- {}".format(path) for path in paths) )) all_classes = { @@ -1023,7 +1023,7 @@ class ApplicationLaunchContext: for path in paths: if not os.path.exists(path): self.log.info( - "Path to launch hooks does not exists: \"{}\"".format(path) + "Path to launch hooks does not exist: \"{}\"".format(path) ) continue @@ -1044,7 +1044,8 @@ class ApplicationLaunchContext: hook = klass(self) if not hook.is_valid: self.log.debug( - "Hook is not valid for current launch context." + "Hook is not valid for current " + "launch context: {}".format(str(hook)) ) continue From e5083dded8e69d2fdb572694ae98eb6983c6cab0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 6 May 2022 10:12:17 +0200 Subject: [PATCH 70/99] added dataclasses to required python modules --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 899e9375c0..d3b967a4b6 100644 --- a/setup.py +++ b/setup.py @@ -106,7 +106,8 @@ install_requires = [ "dns", # Python defaults (cx_Freeze skip them by default) "dbm", - "sqlite3" + "sqlite3", + "dataclasses" ] includes = [] From 840e830b30442830b5f8813d2e40113872c14dee Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 May 2022 11:00:53 +0200 Subject: [PATCH 71/99] Log name of class in a more readable manner --- openpype/lib/applications.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index a84ff990b2..01bd2768ed 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1045,13 +1045,13 @@ class ApplicationLaunchContext: if not hook.is_valid: self.log.debug( "Hook is not valid for current " - "launch context: {}".format(str(hook)) + "launch context: {}".format(klass.__name__) ) continue if inspect.isabstract(hook): self.log.debug("Skipped abstract hook: {}".format( - str(hook) + klass.__name__ )) continue @@ -1063,7 +1063,8 @@ class ApplicationLaunchContext: except Exception: self.log.warning( - "Initialization of hook failed. {}".format(str(klass)), + "Initialization of hook failed: " + "{}".format(klass.__name__), exc_info=True ) From 1eb8300831ccec8ace6d3a030464197b9d35bcb5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 May 2022 11:43:49 +0200 Subject: [PATCH 72/99] Less scary log message --- openpype/lib/applications.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 01bd2768ed..6ade33b59c 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1044,8 +1044,8 @@ class ApplicationLaunchContext: hook = klass(self) if not hook.is_valid: self.log.debug( - "Hook is not valid for current " - "launch context: {}".format(klass.__name__) + "Skipped hook invalid for current launch context: " + "{}".format(klass.__name__) ) continue From 6a123fcdb994aed27c3e0e64f340ccfea9ce05b2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 13:08:34 +0200 Subject: [PATCH 73/99] nuke: change hex type to color_gui in settings --- .../defaults/project_anatomy/imageio.json | 27 ++++++++++++++----- .../schemas/template_nuke_knob_inputs.json | 9 ++++--- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 2023dae23c..c03f5f8ee8 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -75,9 +75,14 @@ "value": true }, { - "type": "hex", + "type": "color_gui", "name": "tile_color", - "value": "0xff0000ff" + "value": [ + 186, + 35, + 35, + 255 + ] }, { "type": "text", @@ -123,9 +128,14 @@ "value": true }, { - "type": "hex", + "type": "color_gui", "name": "tile_color", - "value": "0xadab1dff" + "value": [ + 171, + 171, + 10, + 255 + ] }, { "type": "text", @@ -166,9 +176,14 @@ "value": "Deflate" }, { - "type": "hex", + "type": "color_gui", "name": "tile_color", - "value": "0x23ff00ff" + "value": [ + 56, + 162, + 7, + 255 + ] }, { "type": "text", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json index e6ca1e7fd4..d2fa05e55c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json @@ -65,8 +65,8 @@ ] }, { - "key": "hex", - "label": "Hexadecimal (0x)", + "key": "color_gui", + "label": "Color GUI", "children": [ { "type": "text", @@ -74,9 +74,10 @@ "label": "Name" }, { - "type": "text", + "type": "color", "key": "value", - "label": "Value" + "label": "Value", + "use_alpha": false } ] }, From d640ea3e526c4bb4380505d0d08981e693b8a4b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 13:08:58 +0200 Subject: [PATCH 74/99] nuke: remove redundant code --- openpype/hosts/nuke/plugins/create/create_write_still.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 0b1a942e0e..5b3141355c 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -2,10 +2,6 @@ import nuke from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import create_write_node -from openpype.api import ( - Logger -) -log = Logger.get_logger(__name__) class CreateWriteStill(plugin.AbstractWriteRender): From e7a1c840d02c1dbb8ef8c79b2b9e115df2a9d4ee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 13:09:13 +0200 Subject: [PATCH 75/99] nuke: implementing `color_gui` type --- openpype/hosts/nuke/api/lib.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2d205d0d39..152dfffce7 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1089,10 +1089,10 @@ def create_write_node( iter( k["value"] for k in imageio_writes["knobs"] if "tile_color" in k["name"] - ), "0xff0000ff" + ), [255, 0, 0, 255] ) GN["tile_color"].setValue( - int(tile_color, 16)) + color_gui_to_int(tile_color)) # finally add knob overrides set_node_knobs_from_settings(GN, knob_overrides, **kwargs) @@ -1159,19 +1159,20 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs): knob_value = int(knob_value) elif knob_type == "text": knob_value = knob_value - elif knob_type == "hex": - if not knob_value.startswith("0x"): - raise ValueError( - "Check your settings! Input Hexa is wrong! \n{}".format( - pformat(knob_settings) - )) - knob_value = int(knob_value, 16) + elif knob_type == "color_gui": + knob_value = color_gui_to_int(knob_value) elif knob_type in ["2d_vector", "3d_vector", "color"]: knob_value = [float(v) for v in knob_value] node[knob_name].setValue(knob_value) +def color_gui_to_int(color_gui): + hex_value = ( + "0x{0:0>2x}{1:0>2x}{2:0>2x}{3:0>2x}").format(*color_gui) + return int(hex_value, 16) + + def add_rendering_knobs(node, farm=True): ''' Adds additional rendering knobs to given node From 166ca1b814675b81e02f66998aba4e0a8ded6023 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 May 2022 14:16:04 +0200 Subject: [PATCH 76/99] OP-3113 - added timeit module to requirements psd_tools requires it --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d3b967a4b6..8b5a545c16 100644 --- a/setup.py +++ b/setup.py @@ -107,7 +107,8 @@ install_requires = [ # Python defaults (cx_Freeze skip them by default) "dbm", "sqlite3", - "dataclasses" + "dataclasses", + "timeit" ] includes = [] From b374e8e8a1cb4f3b02c67490d536d639da3abeea Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 14:17:05 +0200 Subject: [PATCH 77/99] nuke: returning legacy code for backward compatibility --- openpype/hosts/nuke/api/lib.py | 380 +++++++++++++++++- openpype/hosts/nuke/api/plugin.py | 17 +- .../plugins/create/create_write_prerender.py | 27 +- .../plugins/create/create_write_render.py | 43 +- .../nuke/plugins/create/create_write_still.py | 48 ++- 5 files changed, 481 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 152dfffce7..25a64a5eef 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -365,6 +365,40 @@ def fix_data_for_node_create(data): return data +def add_write_node_legacy(name, **kwarg): + """Adding nuke write node + Arguments: + name (str): nuke node name + kwarg (attrs): data for nuke knobs + Returns: + node (obj): nuke write node + """ + frame_range = kwarg.get("use_range_limit", None) + + w = nuke.createNode( + "Write", + "name {}".format(name)) + + w["file"].setValue(kwarg["file"]) + + for k, v in kwarg.items(): + if "frame_range" in k: + continue + log.info([k, v]) + try: + w[k].setValue(v) + except KeyError as e: + log.debug(e) + continue + + if frame_range: + w["use_limit"].setValue(True) + w["first"].setValue(frame_range[0]) + w["last"].setValue(frame_range[1]) + + return w + + def add_write_node(name, file_path, knobs, **kwarg): """Adding nuke write node @@ -375,7 +409,7 @@ def add_write_node(name, file_path, knobs, **kwarg): Returns: node (obj): nuke write node """ - frame_range = kwarg.get("frame_range", None) + frame_range = kwarg.get("use_range_limit", None) w = nuke.createNode( "Write", @@ -494,6 +528,80 @@ def get_nuke_imageio_settings(): return get_anatomy_settings(Context.project_name)["imageio"]["nuke"] +def get_created_node_imageio_setting_legacy(nodeclass, creator, subset): + ''' Get preset data for dataflow (fileType, compression, bitDepth) + ''' + + assert any([creator, nodeclass]), nuke.message( + "`{}`: Missing mandatory kwargs `host`, `cls`".format(__file__)) + + imageio_nodes = get_nuke_imageio_settings()["nodes"] + required_nodes = imageio_nodes["requiredNodes"] + override_nodes = imageio_nodes["overrideNodes"] + + imageio_node = None + for node in required_nodes: + log.info(node) + if ( + nodeclass in node["nukeNodeClass"] + and creator in node["plugins"] + ): + imageio_node = node + break + + log.debug("__ imageio_node: {}".format(imageio_node)) + + # find matching override node + override_imageio_node = None + for onode in override_nodes: + log.info(onode) + if nodeclass not in node["nukeNodeClass"]: + continue + + if creator not in node["plugins"]: + continue + + if ( + onode["subsets"] + and not any(re.search(s, subset) for s in onode["subsets"]) + ): + continue + + override_imageio_node = onode + break + + log.debug("__ override_imageio_node: {}".format(override_imageio_node)) + # add overrides to imageio_node + if override_imageio_node: + # get all knob names in imageio_node + knob_names = [k["name"] for k in imageio_node["knobs"]] + + for oknob in override_imageio_node["knobs"]: + for knob in imageio_node["knobs"]: + # override matching knob name + if oknob["name"] == knob["name"]: + log.debug( + "_ overriding knob: `{}` > `{}`".format( + knob, oknob + )) + if not oknob["value"]: + # remove original knob if no value found in oknob + imageio_node["knobs"].remove(knob) + else: + # override knob value with oknob's + knob["value"] = oknob["value"] + + # add missing knobs into imageio_node + if oknob["name"] not in knob_names: + log.debug( + "_ adding knob: `{}`".format(oknob)) + imageio_node["knobs"].append(oknob) + knob_names.append(oknob["name"]) + + log.info("ImageIO node: {}".format(imageio_node)) + return imageio_node + + def get_imageio_node_setting(node_class, plugin_name, subset): ''' Get preset data for dataflow (fileType, compression, bitDepth) ''' @@ -1100,6 +1208,276 @@ def create_write_node( return GN +def create_write_node_legacy(name, data, input=None, prenodes=None, + review=True, linked_knobs=None, farm=True): + ''' Creating write node which is group node + + Arguments: + name (str): name of node + data (dict): data to be imprinted + input (node): selected node to connect to + prenodes (list, optional): list of lists, definitions for nodes + to be created before write + review (bool): adding review knob + + Example: + prenodes = [ + { + "nodeName": { + "class": "" # string + "knobs": [ + ("knobName": value), + ... + ], + "dependent": [ + following_node_01, + ... + ] + } + }, + ... + ] + + Return: + node (obj): group node with avalon data as Knobs + ''' + knob_overrides = data.get("knobs", []) + nodeclass = data["nodeclass"] + creator = data["creator"] + subset = data["subset"] + + imageio_writes = get_created_node_imageio_setting_legacy( + nodeclass, creator, subset + ) + for knob in imageio_writes["knobs"]: + if knob["name"] == "file_type": + representation = knob["value"] + + host_name = os.environ.get("AVALON_APP") + try: + data.update({ + "app": host_name, + "imageio_writes": imageio_writes, + "representation": representation, + }) + anatomy_filled = format_anatomy(data) + + except Exception as e: + msg = "problem with resolving anatomy template: {}".format(e) + log.error(msg) + nuke.message(msg) + + # build file path to workfiles + fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/") + fpath = data["fpath_template"].format( + work=fdir, version=data["version"], subset=data["subset"], + frame=data["frame"], + ext=representation + ) + + # create directory + if not os.path.isdir(os.path.dirname(fpath)): + log.warning("Path does not exist! I am creating it.") + os.makedirs(os.path.dirname(fpath)) + + _data = OrderedDict({ + "file": fpath + }) + + # adding dataflow template + log.debug("imageio_writes: `{}`".format(imageio_writes)) + for knob in imageio_writes["knobs"]: + _data[knob["name"]] = knob["value"] + + _data = fix_data_for_node_create(_data) + + log.debug("_data: `{}`".format(_data)) + + if "frame_range" in data.keys(): + _data["frame_range"] = data.get("frame_range", None) + log.debug("_data[frame_range]: `{}`".format(_data["frame_range"])) + + GN = nuke.createNode("Group", "name {}".format(name)) + + prev_node = None + with GN: + if input: + input_name = str(input.name()).replace(" ", "") + # if connected input node was defined + prev_node = nuke.createNode( + "Input", "name {}".format(input_name)) + else: + # generic input node connected to nothing + prev_node = nuke.createNode( + "Input", "name {}".format("rgba")) + prev_node.hideControlPanel() + # creating pre-write nodes `prenodes` + if prenodes: + for node in prenodes: + # get attributes + pre_node_name = node["name"] + klass = node["class"] + knobs = node["knobs"] + dependent = node["dependent"] + + # create node + now_node = nuke.createNode( + klass, "name {}".format(pre_node_name)) + now_node.hideControlPanel() + + # add data to knob + for _knob in knobs: + knob, value = _knob + try: + now_node[knob].value() + except NameError: + log.warning( + "knob `{}` does not exist on node `{}`".format( + knob, now_node["name"].value() + )) + continue + + if not knob and not value: + continue + + log.info((knob, value)) + + if isinstance(value, str): + if "[" in value: + now_node[knob].setExpression(value) + else: + now_node[knob].setValue(value) + + # connect to previous node + if dependent: + if isinstance(dependent, (tuple or list)): + for i, node_name in enumerate(dependent): + input_node = nuke.createNode( + "Input", "name {}".format(node_name)) + input_node.hideControlPanel() + now_node.setInput(1, input_node) + + elif isinstance(dependent, str): + input_node = nuke.createNode( + "Input", "name {}".format(node_name)) + input_node.hideControlPanel() + now_node.setInput(0, input_node) + + else: + now_node.setInput(0, prev_node) + + # switch actual node to previous + prev_node = now_node + + # creating write node + + write_node = now_node = add_write_node_legacy( + "inside_{}".format(name), + **_data + ) + write_node.hideControlPanel() + # connect to previous node + now_node.setInput(0, prev_node) + + # switch actual node to previous + prev_node = now_node + + now_node = nuke.createNode("Output", "name Output1") + now_node.hideControlPanel() + + # connect to previous node + now_node.setInput(0, prev_node) + + # imprinting group node + set_avalon_knob_data(GN, data["avalon"]) + add_publish_knob(GN) + add_rendering_knobs(GN, farm) + + if review: + add_review_knob(GN) + + # add divider + GN.addKnob(nuke.Text_Knob('', 'Rendering')) + + # Add linked knobs. + linked_knob_names = [] + + # add input linked knobs and create group only if any input + if linked_knobs: + linked_knob_names.append("_grp-start_") + linked_knob_names.extend(linked_knobs) + linked_knob_names.append("_grp-end_") + + linked_knob_names.append("Render") + + for _k_name in linked_knob_names: + if "_grp-start_" in _k_name: + knob = nuke.Tab_Knob( + "rnd_attr", "Rendering attributes", nuke.TABBEGINCLOSEDGROUP) + GN.addKnob(knob) + elif "_grp-end_" in _k_name: + knob = nuke.Tab_Knob( + "rnd_attr_end", "Rendering attributes", nuke.TABENDGROUP) + GN.addKnob(knob) + else: + if "___" in _k_name: + # add divider + GN.addKnob(nuke.Text_Knob("")) + else: + # add linked knob by _k_name + link = nuke.Link_Knob("") + link.makeLink(write_node.name(), _k_name) + link.setName(_k_name) + + # make render + if "Render" in _k_name: + link.setLabel("Render Local") + link.setFlag(0x1000) + GN.addKnob(link) + + # adding write to read button + add_button_write_to_read(GN) + + # adding write to read button + add_button_clear_rendered(GN, os.path.dirname(fpath)) + + # Deadline tab. + add_deadline_tab(GN) + + # open the our Tab as default + GN[_NODE_TAB_NAME].setFlag(0) + + # set tile color + tile_color = _data.get("tile_color", "0xff0000ff") + GN["tile_color"].setValue(tile_color) + + # overrie knob values from settings + for knob in knob_overrides: + knob_type = knob["type"] + knob_name = knob["name"] + knob_value = knob["value"] + if knob_name not in GN.knobs(): + continue + if not knob_value: + continue + + # set correctly knob types + if knob_type == "string": + knob_value = str(knob_value) + if knob_type == "number": + knob_value = int(knob_value) + if knob_type == "decimal_number": + knob_value = float(knob_value) + if knob_type == "bool": + knob_value = bool(knob_value) + if knob_type in ["2d_vector", "3d_vector"]: + knob_value = list(knob_value) + + GN[knob_name].setValue(knob_value) + + return GN + + def set_node_knobs_from_settings(node, knob_settings, **kwargs): """ Overriding knob values from settings diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 1eaccef795..76c1e7a37c 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -17,7 +17,8 @@ from .lib import ( reset_selection, maintained_selection, set_avalon_knob_data, - add_publish_knob + add_publish_knob, + get_nuke_imageio_settings ) @@ -700,6 +701,20 @@ class AbstractWriteRender(OpenPypeCreator): return write_node + def is_legacy(self): + """Check if it needs to run legacy code + + In case where `type` key is missing in singe + knob it is legacy project anatomy. + + Returns: + bool: True if legacy + """ + imageio_nodes = get_nuke_imageio_settings()["nodes"] + node = imageio_nodes["requiredNodes"][0] + if "type" not in node["knobs"][0]: + return True + @abstractmethod def _create_write_node(self, selected_node, inputs, outputs, write_data): """Family dependent implementation of Write node creation diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index f86ed7b89e..32ee1fd86f 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -1,7 +1,8 @@ import nuke from openpype.hosts.nuke.api import plugin -from openpype.hosts.nuke.api.lib import create_write_node +from openpype.hosts.nuke.api.lib import ( + create_write_node, create_write_node_legacy) class CreateWritePrerender(plugin.AbstractWriteRender): @@ -25,14 +26,24 @@ class CreateWritePrerender(plugin.AbstractWriteRender): def _create_write_node(self, selected_node, inputs, outputs, write_data): # add fpath_template write_data["fpath_template"] = self.fpath_template + write_data["use_range_limit"] = self.use_range_limit - return create_write_node( - self.data["subset"], - write_data, - input=selected_node, - review=self.reviewable, - linked_knobs=["channels", "___", "first", "last", "use_limit"] - ) + if not self.is_legacy(): + return create_write_node( + self.data["subset"], + write_data, + input=selected_node, + review=self.reviewable, + linked_knobs=["channels", "___", "first", "last", "use_limit"] + ) + else: + return create_write_node_legacy( + self.data["subset"], + write_data, + input=selected_node, + review=self.reviewable, + linked_knobs=["channels", "___", "first", "last", "use_limit"] + ) def _modify_write_node(self, write_node): # open group node diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 43b1a9dcb4..23846c0332 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -1,7 +1,8 @@ import nuke from openpype.hosts.nuke.api import plugin -from openpype.hosts.nuke.api.lib import create_write_node +from openpype.hosts.nuke.api.lib import ( + create_write_node, create_write_node_legacy) class CreateWriteRender(plugin.AbstractWriteRender): @@ -50,16 +51,36 @@ class CreateWriteRender(plugin.AbstractWriteRender): actual_format = nuke.root().knob('format').value() width, height = (actual_format.width(), actual_format.height()) - return create_write_node( - self.data["subset"], - write_data, - input=selected_node, - prenodes=self.prenodes, - **{ - "width": width, - "height": height - } - ) + if not self.is_legacy(): + return create_write_node( + self.data["subset"], + write_data, + input=selected_node, + prenodes=self.prenodes, + **{ + "width": width, + "height": height + } + ) + else: + _prenodes = [ + { + "name": "Reformat01", + "class": "Reformat", + "knobs": [ + ("resize", 0), + ("black_outside", 1), + ], + "dependent": None + } + ] + + return create_write_node_legacy( + self.data["subset"], + write_data, + input=selected_node, + prenodes=_prenodes + ) def _modify_write_node(self, write_node): return write_node diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 5b3141355c..4007ccf51e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -1,7 +1,8 @@ import nuke from openpype.hosts.nuke.api import plugin -from openpype.hosts.nuke.api.lib import create_write_node +from openpype.hosts.nuke.api.lib import ( + create_write_node, create_write_node_legacy) class CreateWriteStill(plugin.AbstractWriteRender): @@ -42,18 +43,39 @@ class CreateWriteStill(plugin.AbstractWriteRender): # add fpath_template write_data["fpath_template"] = self.fpath_template - return create_write_node( - self.name, - write_data, - input=selected_node, - review=False, - prenodes=self.prenodes, - farm=False, - linked_knobs=["channels", "___", "first", "last", "use_limit"], - **{ - "frame": nuke.frame() - } - ) + if not self.is_legacy(): + return create_write_node( + self.name, + write_data, + input=selected_node, + review=False, + prenodes=self.prenodes, + farm=False, + linked_knobs=["channels", "___", "first", "last", "use_limit"], + **{ + "frame": nuke.frame() + } + ) + else: + _prenodes = [ + { + "name": "FrameHold01", + "class": "FrameHold", + "knobs": [ + ("first_frame", nuke.frame()) + ], + "dependent": None + } + ] + return create_write_node_legacy( + self.name, + write_data, + input=selected_node, + review=False, + prenodes=_prenodes, + farm=False, + linked_knobs=["channels", "___", "first", "last", "use_limit"] + ) def _modify_write_node(self, write_node): write_node.begin() From 89499e97a38f75386bb10a4452bb8ebe225dd88f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 15:04:29 +0200 Subject: [PATCH 78/99] nuke: skipping override if no node was found --- openpype/hosts/nuke/api/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 25a64a5eef..0025141310 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -620,6 +620,9 @@ def get_imageio_node_setting(node_class, plugin_name, subset): log.debug("__ imageio_node: {}".format(imageio_node)) + if not imageio_node: + return + # find overrides and update knobs with them get_imageio_node_override_setting( node_class, From f14cabb667b06d5a5501f5dd4564193ae34967ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 15:15:07 +0200 Subject: [PATCH 79/99] hound --- openpype/hosts/nuke/api/lib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ff3932c63d..ba8aa7a8db 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1211,8 +1211,10 @@ def create_write_node( return GN -def create_write_node_legacy(name, data, input=None, prenodes=None, - review=True, linked_knobs=None, farm=True): +def create_write_node_legacy( + name, data, input=None, prenodes=None, + review=True, linked_knobs=None, farm=True +): ''' Creating write node which is group node Arguments: From a0284fd23947f8c4fc882947b86134227dc5d3c6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 May 2022 15:41:55 +0200 Subject: [PATCH 80/99] nuke: adding legacy type --- openpype/hosts/nuke/api/plugin.py | 8 ++++++++ .../schemas/template_nuke_knob_inputs.json | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 76c1e7a37c..2bad6f2c78 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -713,6 +713,14 @@ class AbstractWriteRender(OpenPypeCreator): imageio_nodes = get_nuke_imageio_settings()["nodes"] node = imageio_nodes["requiredNodes"][0] if "type" not in node["knobs"][0]: + # if type is not yet in project anatomy + return True + elif next(iter( + _k for _k in node["knobs"] + if _k.get("type") == "__legacy__" + ), None): + # in case someone re-saved anatomy + # with old configuration return True @abstractmethod diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json index d2fa05e55c..52a14e0636 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_nuke_knob_inputs.json @@ -251,6 +251,22 @@ ] } ] + }, + { + "key": "__legacy__", + "label": "_ Legacy type _", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] } ] } From e87024d7ff830d2229cd25753e917d586557ee5e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 6 May 2022 16:26:15 +0200 Subject: [PATCH 81/99] added backwards compatibility for imageio values --- openpype/settings/lib.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index f921b9c318..f1a4541850 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -291,6 +291,22 @@ def _system_settings_backwards_compatible_conversion(studio_overrides): } +def _project_anatomy_backwards_compatible_conversion(project_anatomy): + # Backwards compatibility of node settings in Nuke 3.9.x - 3.10.0 + # - source PR - https://github.com/pypeclub/OpenPype/pull/3143 + value = project_anatomy + for key in ("imageio", "nuke", "nodes", "requiredNodes"): + if key not in value: + return + value = value[key] + + for item in value: + for node in item.get("requiredNodes") or []: + if "type" in node: + break + node["type"] = "__legacy__" + + @require_handler def get_studio_system_settings_overrides(return_version=False): output = _SETTINGS_HANDLER.get_studio_system_settings_overrides( @@ -326,7 +342,9 @@ def get_project_settings_overrides(project_name, return_version=False): @require_handler def get_project_anatomy_overrides(project_name): - return _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name) + output = _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name) + _project_anatomy_backwards_compatible_conversion(output) + return output @require_handler From b9c211c4b5e37fa31ac6058e040bf9656d3deae5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 6 May 2022 16:27:53 +0200 Subject: [PATCH 82/99] fixed order of variables in ftrack action delete old versions --- .../ftrack/event_handlers_user/action_delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index f5addde8ae..a0bf6622e9 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -569,7 +569,7 @@ class DeleteOldVersions(BaseAction): context["frame"] = self.sequence_splitter sequence_path = os.path.normpath( StringTemplate.format_strict_template( - context, template + template, context ) ) From 065964525ad367c8d536cd8768a250e1af5a0f98 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 6 May 2022 16:53:43 +0200 Subject: [PATCH 83/99] fix key access --- openpype/settings/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index f1a4541850..6df41112c8 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -301,7 +301,7 @@ def _project_anatomy_backwards_compatible_conversion(project_anatomy): value = value[key] for item in value: - for node in item.get("requiredNodes") or []: + for node in item.get("knobs") or []: if "type" in node: break node["type"] = "__legacy__" From ec49131a5b6647a45718574e7a1628363a03ec04 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 6 May 2022 18:18:05 +0200 Subject: [PATCH 84/99] add support for compressed bgeo to standalone and simple loader to houdini --- .../hosts/houdini/plugins/load/load_bgeo.py | 107 ++++++++++++++++++ .../widgets/widget_drop_frame.py | 2 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/houdini/plugins/load/load_bgeo.py diff --git a/openpype/hosts/houdini/plugins/load/load_bgeo.py b/openpype/hosts/houdini/plugins/load/load_bgeo.py new file mode 100644 index 0000000000..a463d51383 --- /dev/null +++ b/openpype/hosts/houdini/plugins/load/load_bgeo.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +import os +import re + +from openpype.pipeline import ( + load, + get_representation_path, +) +from openpype.hosts.houdini.api import pipeline + + +class BgeoLoader(load.LoaderPlugin): + """Load bgeo files to Houdini.""" + + label = "Load bgeo" + families = ["model", "pointcache", "bgeo"] + representations = [ + "bgeo", "bgeosc", "bgeogz", + "bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + import hou + + # Get the root node + obj = hou.node("/obj") + + # Define node name + namespace = namespace if namespace else context["asset"]["name"] + node_name = "{}_{}".format(namespace, name) if namespace else name + + # Create a new geo node + container = obj.createNode("geo", node_name=node_name) + is_sequence = bool(context["representation"]["context"].get("frame")) + + # Remove the file node, it only loads static meshes + # Houdini 17 has removed the file node from the geo node + file_node = container.node("file1") + if file_node: + file_node.destroy() + + # Explicitly create a file node + file_node = container.createNode("file", node_name=node_name) + file_node.setParms({"file": self.format_path(self.fname, is_sequence)}) + + # Set display on last node + file_node.setDisplayFlag(True) + + nodes = [container, file_node] + self[:] = nodes + + return pipeline.containerise( + node_name, + namespace, + nodes, + context, + self.__class__.__name__, + suffix="", + ) + + @staticmethod + def format_path(path, is_sequence): + """Format file path correctly for single bgeo or bgeo sequence.""" + if not os.path.exists(path): + raise RuntimeError("Path does not exist: %s" % path) + + # The path is either a single file or sequence in a folder. + if not is_sequence: + filename = path + print("single") + else: + filename = re.sub(r"(.*)\.(\d+)\.(bgeo.*)", "\\1.$F4.\\3", path) + + filename = os.path.join(path, filename) + + filename = os.path.normpath(filename) + filename = filename.replace("\\", "/") + + return filename + + def update(self, container, representation): + + node = container["node"] + try: + file_node = next( + n for n in node.children() if n.type().name() == "file" + ) + except StopIteration: + self.log.error("Could not find node of type `alembic`") + return + + # Update the file path + file_path = get_representation_path(representation) + file_path = self.format_path(file_path) + + file_node.setParms({"fileName": file_path}) + + # Update attribute + node.setParms({"representation": str(representation["_id"])}) + + def remove(self, container): + + node = container["node"] + node.destroy() diff --git a/openpype/tools/standalonepublish/widgets/widget_drop_frame.py b/openpype/tools/standalonepublish/widgets/widget_drop_frame.py index e6c7328e88..f8a8273b26 100644 --- a/openpype/tools/standalonepublish/widgets/widget_drop_frame.py +++ b/openpype/tools/standalonepublish/widgets/widget_drop_frame.py @@ -38,7 +38,7 @@ class DropDataFrame(QtWidgets.QFrame): } sequence_types = [ - ".bgeo", ".vdb" + ".bgeo", ".vdb", ".bgeosc", ".bgeogz" ] def __init__(self, parent): From d746f9410572b2dab73e31c75037561710f31b32 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 6 May 2022 18:41:17 +0200 Subject: [PATCH 85/99] add comment and get renderer from instance --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7bf68f51ee..8562c85f7d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -266,7 +266,6 @@ def get_renderer_variables(renderlayer, root): filename_prefix = cmds.getAttr(prefix_attr) return {"ext": extension, - "renderer": renderer, "filename_prefix": filename_prefix, "padding": padding, "filename_0": filename_0} @@ -441,7 +440,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): output_filename_0 = filename_0 - if render_variables["renderer"] == "renderman": + # this is needed because renderman handles directory and file + # prefixes separately + if self._instance.data["renderer"] == "renderman": dirname = os.path.dirname(output_filename_0) # Create render folder ---------------------------------------------- From 7226d82c8f0b6ab52ed7d0a95e7c61deb9e81015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 9 May 2022 15:10:20 +0200 Subject: [PATCH 86/99] add support for hw prefixes --- openpype/hosts/maya/plugins/create/create_render.py | 6 ++++-- openpype/hosts/maya/plugins/publish/collect_render.py | 4 ++-- .../plugins/publish/validate_render_single_camera.py | 3 ++- .../maya/plugins/publish/validate_rendersettings.py | 10 ++++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 8e9bd0e22b..93ee6679e5 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -77,7 +77,8 @@ class CreateRender(plugin.Creator): 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', 'renderman': 'rmanGlobals.imageFileFormat', - 'redshift': 'defaultRenderGlobals.imageFilePrefix' + 'redshift': 'defaultRenderGlobals.imageFilePrefix', + 'mayahardware2': 'defaultRenderGlobals.imageFilePrefix', } _image_prefixes = { @@ -87,7 +88,8 @@ class CreateRender(plugin.Creator): # this needs `imageOutputDir` # (/renders/maya/) set separately 'renderman': '_..', - 'redshift': 'maya///' # noqa + 'redshift': 'maya///', # noqa + 'mayahardware2': 'maya///', # noqa } _aov_chars = { diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 912fe179dd..e66983780e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -326,8 +326,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "byFrameStep": int( self.get_render_attribute("byFrameStep", layer=layer_name)), - "renderer": self.get_render_attribute("currentRenderer", - layer=layer_name), + "renderer": self.get_render_attribute( + "currentRenderer", layer=layer_name).lower(), # instance subset "family": "renderlayer", "families": ["renderlayer"], diff --git a/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py b/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py index 0838b4fbf8..e6c6ef6c9e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py @@ -12,7 +12,8 @@ ImagePrefixes = { 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', 'renderman': 'defaultRenderGlobals.imageFilePrefix', - 'redshift': 'defaultRenderGlobals.imageFilePrefix' + 'redshift': 'defaultRenderGlobals.imageFilePrefix', + 'mayahardware2': 'defaultRenderGlobals.imageFilePrefix', } diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 023e27de17..e212e8978d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -50,15 +50,17 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', 'renderman': 'rmanGlobals.imageFileFormat', - 'redshift': 'defaultRenderGlobals.imageFilePrefix' + 'redshift': 'defaultRenderGlobals.imageFilePrefix', + 'mayahardware2': 'defaultRenderGlobals.imageFilePrefix', } ImagePrefixTokens = { - + 'mentalray': 'maya///{aov_separator}', 'arnold': 'maya///{aov_separator}', # noqa 'redshift': 'maya///', 'vray': 'maya///', - 'renderman': '{aov_separator}..' # noqa + 'renderman': '{aov_separator}..', # noqa + 'mayahardware2': 'maya///', # noqa } _aov_chars = { @@ -234,7 +236,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # load validation definitions from settings validation_settings = ( instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( # noqa: E501 - "{}_render_attributes".format(renderer)) + "{}_render_attributes".format(renderer)) or [] ) # go through definitions and test if such node.attribute exists. From 10e4764da21de5aec20df48f619c02a06644cdc7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 10 May 2022 11:15:42 +0200 Subject: [PATCH 87/99] OP-3113 - removed unnecessary default Task type enum is dynamically created from Ftrack, value here is targeted only for single customer, so it shouldn't probably be in defaults at all. --- openpype/settings/defaults/project_anatomy/tasks.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/tasks.json b/openpype/settings/defaults/project_anatomy/tasks.json index 178f2af639..74504cc4d7 100644 --- a/openpype/settings/defaults/project_anatomy/tasks.json +++ b/openpype/settings/defaults/project_anatomy/tasks.json @@ -40,8 +40,5 @@ }, "Compositing": { "short_name": "comp" - }, - "Background": { - "short_name": "back" } } \ No newline at end of file From 7ff23fd4294532d670adffe179c8ea893db9feda Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 10 May 2022 12:06:08 +0200 Subject: [PATCH 88/99] fix extension collection --- .../traypublisher/plugins/publish/collect_simple_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 9facd90a48..b2be43c701 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -33,7 +33,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): instance.data["stagingDir"] = filepath_item["directory"] filenames = filepath_item["filenames"] - ext = os.path.splitext(filenames[0])[-1] + _, ext = os.path.splitext(filenames[0]) + ext = ext[1:] if len(filenames) == 1: filenames = filenames[0] From 235baf4f4c40fc92f3faf148d846655590bf6cdb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 10 May 2022 14:48:53 +0200 Subject: [PATCH 89/99] store width and height into thumbnail representation --- .../plugins/publish/extract_thumbnail.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py index 23f0b104c8..941a76b05b 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py @@ -2,7 +2,10 @@ import os import tempfile import pyblish.api import openpype.api -import openpype.lib +from openpype.lib import ( + get_ffmpeg_tool_path, + get_ffprobe_streams, +) class ExtractThumbnailSP(pyblish.api.InstancePlugin): @@ -71,7 +74,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin): full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] self.log.info("output {}".format(full_thumbnail_path)) - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} @@ -110,6 +113,13 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin): # remove thumbnail key from origin repre thumbnail_repre.pop("thumbnail") + streams = get_ffprobe_streams(full_thumbnail_path) + width = height = None + for stream in streams: + if "width" in stream and "height" in stream: + width = stream["width"] + height = stream["height"] + break filename = os.path.basename(full_thumbnail_path) staging_dir = staging_dir or os.path.dirname(full_thumbnail_path) @@ -122,6 +132,9 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin): "stagingDir": staging_dir, "tags": ["thumbnail"], } + if width and height: + representation["width"] = width + representation["height"] = height # # add Delete tag when temp file was rendered if not is_jpeg: From 5da19f09ab487419f15a2179394a923a037e9ef4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 10 May 2022 14:49:37 +0200 Subject: [PATCH 90/99] integrate ftrack instance is collecting width and height from representation and uses ffprobe if are not available --- .../publish/integrate_ftrack_instances.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 5eecf34c3d..7181be6f01 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -3,6 +3,7 @@ import json import copy import pyblish.api +from openpype.lib import get_ffprobe_streams from openpype.lib.profiles_filtering import filter_profiles @@ -142,6 +143,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create thumbnail components # TODO what if there is multiple thumbnails? first_thumbnail_component = None + first_thumbnail_component_repre = None for repre in thumbnail_representations: published_path = repre.get("published_path") if not published_path: @@ -169,12 +171,45 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): src_components_to_add.append(copy.deepcopy(thumbnail_item)) # Create copy of first thumbnail if first_thumbnail_component is None: - first_thumbnail_component = copy.deepcopy(thumbnail_item) + first_thumbnail_component_repre = repre + first_thumbnail_component = thumbnail_item # Set location thumbnail_item["component_location"] = ftrack_server_location # Add item to component list component_list.append(thumbnail_item) + if first_thumbnail_component is not None: + width = first_thumbnail_component_repre.get("width") + height = first_thumbnail_component_repre.get("height") + if not width or not height: + component_path = first_thumbnail_component["component_path"] + streams = [] + try: + streams = get_ffprobe_streams(component_path) + except Exception: + self.log.debug( + "Failed to retrieve information about intput {}".format( + component_path + ) + ) + + for stream in streams: + if "width" in stream and "height" in stream: + width = stream["width"] + height = stream["height"] + break + + if width and height: + component_data = first_thumbnail_component["component_data"] + component_data["name"] = "ftrackreview-image" + component_data["metadata"] = { + "ftr_meta": json.dumps({ + "width": width, + "height": height, + "format": "image" + }) + } + # Create review components # Change asset name of each new component for review is_first_review_repre = True From a20891e5687514b2852440948c44a9b5cf0da3b4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 10 May 2022 15:17:58 +0200 Subject: [PATCH 91/99] fix line length --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 7181be6f01..0dd7b1c6e4 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -187,11 +187,9 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): try: streams = get_ffprobe_streams(component_path) except Exception: - self.log.debug( - "Failed to retrieve information about intput {}".format( - component_path - ) - ) + self.log.debug(( + "Failed to retrieve information about intput {}" + ).format(component_path)) for stream in streams: if "width" in stream and "height" in stream: From b80bb7f7df51d43ec0f15d80076fba3a90077f51 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 10 May 2022 15:36:27 +0200 Subject: [PATCH 92/99] OP-3137 - use validation settings to create subset name Uses Setting to replace invalid characters automatically. Creates valid subset name and renames layer. --- .../publish/collect_color_coded_instances.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index 122428eea0..ca8b5d962f 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -5,6 +5,7 @@ import pyblish.api from openpype.lib import prepare_template_data from openpype.hosts.photoshop import api as photoshop +from openpype.settings import get_project_settings class CollectColorCodedInstances(pyblish.api.ContextPlugin): @@ -49,6 +50,12 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): asset_name = context.data["asset"] task_name = context.data["task"] variant = context.data["variant"] + project_name = context.data["projectEntity"]["name"] + + naming_conventions = get_project_settings(project_name).get( + "photoshop", {}).get( + "publish", {}).get( + "ValidateNaming", {}) stub = photoshop.stub() layers = stub.get_layers() @@ -83,6 +90,9 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): subset = resolved_subset_template.format( **prepare_template_data(fill_pairs)) + subset = self._clean_subset_name(stub, naming_conventions, + subset, layer) + if subset in existing_subset_names: self.log.info( "Subset {} already created, skipping.".format(subset)) @@ -186,3 +196,21 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): self.log.debug("resolved_subset_template {}".format( resolved_subset_template)) return family, resolved_subset_template + + def _clean_subset_name(self, stub, naming_conventions, subset, layer): + """Cleans invalid characters from subset name and layer name.""" + if re.search(naming_conventions["invalid_chars"], subset): + subset = re.sub( + naming_conventions["invalid_chars"], + naming_conventions["replace_char"], + subset + ) + layer_name = re.sub( + naming_conventions["invalid_chars"], + naming_conventions["replace_char"], + layer.clean_name + ) + layer.name = layer_name + stub.rename_layer(layer.id, layer_name) + + return subset From 734258cc195f2dee0cd7481b64b88bb3bcdbd261 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 10 May 2022 15:58:35 +0200 Subject: [PATCH 93/99] OP-3137 - use validation settings to create subset name Uses Setting to replace invalid characters automatically. Creates valid subset name and renames layer. --- .../photoshop/plugins/publish/collect_color_coded_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index ca8b5d962f..ae025fc61d 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -151,6 +151,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): instance.data["task"] = task_name instance.data["subset"] = subset instance.data["layer"] = layer + instance.data["families"] = [] return instance From edef0cca11c0cdf22097a33cdb786abd4762eca9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 10 May 2022 14:32:18 +0200 Subject: [PATCH 94/99] OP-3137 - fix naming validator Layer name validator was looking into wrong field. Layer name should follow same naming convention as subset name to see relation between subset name and layer name better. Introduced clean_name property returning layer name without publishing highlight icons. --- openpype/hosts/photoshop/api/ws_stub.py | 10 ++++++++++ .../plugins/publish/validate_naming.py | 19 ++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/photoshop/api/ws_stub.py b/openpype/hosts/photoshop/api/ws_stub.py index fa076ecc7e..b49bf1c73f 100644 --- a/openpype/hosts/photoshop/api/ws_stub.py +++ b/openpype/hosts/photoshop/api/ws_stub.py @@ -29,6 +29,16 @@ class PSItem(object): color_code = attr.ib(default=None) # color code of layer instance_id = attr.ib(default=None) + @property + def clean_name(self): + """Returns layer name without publish icon highlight + + Returns: + (str) + """ + return (self.name.replace(PhotoshopServerStub.PUBLISH_ICON, '') + .replace(PhotoshopServerStub.LOADED_ICON, '')) + class PhotoshopServerStub: """ diff --git a/openpype/hosts/photoshop/plugins/publish/validate_naming.py b/openpype/hosts/photoshop/plugins/publish/validate_naming.py index bcae24108c..b53f4e8198 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_naming.py @@ -42,7 +42,8 @@ class ValidateNamingRepair(pyblish.api.Action): layer_name = re.sub(invalid_chars, replace_char, - current_layer_state.name) + current_layer_state.clean_name) + layer_name = stub.PUBLISH_ICON + layer_name stub.rename_layer(current_layer_state.id, layer_name) @@ -73,13 +74,17 @@ class ValidateNaming(pyblish.api.InstancePlugin): def process(self, instance): help_msg = ' Use Repair action (A) in Pyblish to fix it.' - msg = "Name \"{}\" is not allowed.{}".format(instance.data["name"], - help_msg) - formatting_data = {"msg": msg} - if re.search(self.invalid_chars, instance.data["name"]): - raise PublishXmlValidationError(self, msg, - formatting_data=formatting_data) + layer = instance.data.get("layer") + if layer: + msg = "Name \"{}\" is not allowed.{}".format(layer.clean_name, + help_msg) + + formatting_data = {"msg": msg} + if re.search(self.invalid_chars, layer.clean_name): + raise PublishXmlValidationError(self, msg, + formatting_data=formatting_data + ) msg = "Subset \"{}\" is not allowed.{}".format(instance.data["subset"], help_msg) From dbbc633e834beabb99d0045939b03b28d98d94c9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 11 May 2022 10:36:41 +0200 Subject: [PATCH 95/99] Fix - added missing task Task used in validations later. --- openpype/hosts/harmony/plugins/publish/collect_farm_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py index f5bf051243..3e9e680efd 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py @@ -144,6 +144,7 @@ class CollectFarmRender(openpype.lib.abstract_collect_render. label=node.split("/")[1], subset=subset_name, asset=legacy_io.Session["AVALON_ASSET"], + task=task_name, attachTo=False, setMembers=[node], publish=info[4], From 45a1cb821e6a5fdea8cba16dfdbff842c4aa25c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 11 May 2022 11:48:39 +0200 Subject: [PATCH 96/99] Update validate_rendersettings.py --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index e212e8978d..ba6c1397ab 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -55,12 +55,12 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): } ImagePrefixTokens = { - 'mentalray': 'maya///{aov_separator}', - 'arnold': 'maya///{aov_separator}', # noqa + 'mentalray': 'maya///{aov_separator}', # noqa: E501 + 'arnold': 'maya///{aov_separator}', # noqa: E501 'redshift': 'maya///', 'vray': 'maya///', - 'renderman': '{aov_separator}..', # noqa - 'mayahardware2': 'maya///', # noqa + 'renderman': '{aov_separator}..', + 'mayahardware2': 'maya///', } _aov_chars = { From 497094cabc84184143969ecab9e4473df252745e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 11 May 2022 14:30:44 +0200 Subject: [PATCH 97/99] Remove invalid submodules --- openpype/modules/default_modules/ftrack/python2_vendor/arrow | 1 - .../default_modules/ftrack/python2_vendor/ftrack-python-api | 1 - repos/avalon-core | 1 - repos/avalon-unreal-integration | 1 - 4 files changed, 4 deletions(-) delete mode 160000 openpype/modules/default_modules/ftrack/python2_vendor/arrow delete mode 160000 openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api delete mode 160000 repos/avalon-core delete mode 160000 repos/avalon-unreal-integration diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/arrow b/openpype/modules/default_modules/ftrack/python2_vendor/arrow deleted file mode 160000 index b746fedf72..0000000000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/arrow +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api deleted file mode 160000 index d277f474ab..0000000000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e diff --git a/repos/avalon-core b/repos/avalon-core deleted file mode 160000 index 159d2f23e4..0000000000 --- a/repos/avalon-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 159d2f23e4c79c04dfac57b68d2ee6ac67adec1b diff --git a/repos/avalon-unreal-integration b/repos/avalon-unreal-integration deleted file mode 160000 index 43f6ea9439..0000000000 --- a/repos/avalon-unreal-integration +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 43f6ea943980b29c02a170942b566ae11f2b7080 From 7b225feb13778872f52a56bc5cc0a54d6d652d4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 11 May 2022 16:36:29 +0200 Subject: [PATCH 98/99] it is properly checked for not allowed characters --- openpype/lib/transcoding.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index f20bef3854..526a73de08 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -624,14 +624,13 @@ def convert_input_paths_for_ffmpeg( len(attr_value) ) - if erase_attribute: - for char in NOT_ALLOWED_FFMPEG_CHARS: - if char in attr_value: - erase_attribute = True - erase_reason = ( - "contains unsupported character \"{}\"." - ).format(char) - break + for char in NOT_ALLOWED_FFMPEG_CHARS: + if char in attr_value: + erase_attribute = True + erase_reason = ( + "contains unsupported character \"{}\"." + ).format(char) + break if erase_attribute: # Set attribute to empty string From 9640a7463bb4cd7c22bbce35bf28de1c1c6809e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 11 May 2022 17:21:40 +0200 Subject: [PATCH 99/99] fix not allowed characters properly --- openpype/lib/transcoding.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 526a73de08..adb9bb2c3a 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -493,8 +493,9 @@ def convert_for_ffmpeg( erase_reason = "has too long value ({} chars).".format( len(attr_value) ) + erase_attribute = True - if erase_attribute: + if not erase_attribute: for char in NOT_ALLOWED_FFMPEG_CHARS: if char in attr_value: erase_attribute = True @@ -623,14 +624,16 @@ def convert_input_paths_for_ffmpeg( erase_reason = "has too long value ({} chars).".format( len(attr_value) ) + erase_attribute = True - for char in NOT_ALLOWED_FFMPEG_CHARS: - if char in attr_value: - erase_attribute = True - erase_reason = ( - "contains unsupported character \"{}\"." - ).format(char) - break + if not erase_attribute: + for char in NOT_ALLOWED_FFMPEG_CHARS: + if char in attr_value: + erase_attribute = True + erase_reason = ( + "contains unsupported character \"{}\"." + ).format(char) + break if erase_attribute: # Set attribute to empty string